-- ============================================================
-- Classroom Copilot — Academic Calendar Source of Truth
-- Migration 003: Supabase-backed academic calendar & timetable tables
-- Run after: 002_schema.sql
--
-- Design: Supabase is the source of truth for all editable calendar
-- and timetable data. Neo4j is a derived graph rebuilt from these tables.
-- All tables include neo4j_node_id to track the corresponding Neo4j uuid_string.
-- ============================================================

-- ─── 1. school_timetables ────────────────────────────────────────────────────
-- One row per academic year configuration per school.
-- periods_template JSONB stores the period definitions (code, name, times, type).

CREATE TABLE IF NOT EXISTS school_timetables (
    id                UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    institute_id      UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    year_label        TEXT NOT NULL,           -- e.g. '2025-2026'
    start_date        DATE NOT NULL,
    end_date          DATE NOT NULL,
    periods_template  JSONB NOT NULL DEFAULT '[]',
    neo4j_node_id     TEXT,                    -- SchoolTimetable.uuid_string in Neo4j
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (institute_id, year_label)
);

-- ─── 2. academic_years ───────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS academic_years (
    id                   UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    school_timetable_id  UUID NOT NULL REFERENCES school_timetables(id) ON DELETE CASCADE,
    institute_id         UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    year_label           TEXT NOT NULL,        -- '2025-2026'
    neo4j_node_id        TEXT,
    created_at           TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (school_timetable_id, year_label)
);

-- ─── 3. academic_terms ───────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS academic_terms (
    id               UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    academic_year_id UUID NOT NULL REFERENCES academic_years(id) ON DELETE CASCADE,
    institute_id     UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    term_name        TEXT NOT NULL,
    term_number      INTEGER NOT NULL,
    start_date       DATE NOT NULL,
    end_date         DATE NOT NULL,
    neo4j_node_id    TEXT,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (academic_year_id, term_number)
);

-- ─── 4. academic_weeks ───────────────────────────────────────────────────────
-- week_cycle 'A'|'B' for two-week timetable cycles.

CREATE TABLE IF NOT EXISTS academic_weeks (
    id                UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    academic_term_id  UUID NOT NULL REFERENCES academic_terms(id) ON DELETE CASCADE,
    institute_id      UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    week_number       INTEGER NOT NULL,        -- sequential within term
    start_date        DATE NOT NULL,           -- Monday of this week
    week_cycle        TEXT NOT NULL DEFAULT 'A' CHECK (week_cycle IN ('A', 'B', '')),
    neo4j_node_id     TEXT,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (academic_term_id, week_number)
);

-- ─── 5. academic_days ────────────────────────────────────────────────────────
-- One row per school day (Mon–Fri within term bounds).
-- excluded_period_codes: period codes from the template that do NOT apply this day.
-- academic_day_number: sequential count of Academic-type days across the year.

CREATE TABLE IF NOT EXISTS academic_days (
    id                    UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    academic_week_id      UUID NOT NULL REFERENCES academic_weeks(id) ON DELETE CASCADE,
    academic_term_id      UUID NOT NULL REFERENCES academic_terms(id) ON DELETE CASCADE,
    institute_id          UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    date                  DATE NOT NULL,
    day_of_week           TEXT NOT NULL,
    day_type              TEXT NOT NULL DEFAULT 'Academic'
                              CHECK (day_type IN ('Academic', 'Holiday', 'Staff', 'OffTimetable')),
    academic_day_number   INTEGER,             -- null for non-Academic days
    excluded_period_codes TEXT[] NOT NULL DEFAULT '{}',
    neo4j_node_id         TEXT,
    created_at            TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (institute_id, date)
);

-- ─── 6. teacher_timetables ───────────────────────────────────────────────────
-- One per teacher per academic year.

CREATE TABLE IF NOT EXISTS teacher_timetables (
    id                   UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    profile_id           UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    institute_id         UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    school_timetable_id  UUID NOT NULL REFERENCES school_timetables(id) ON DELETE CASCADE,
    start_date           DATE NOT NULL,
    end_date             DATE NOT NULL,
    neo4j_node_id        TEXT,
    created_at           TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at           TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (profile_id, school_timetable_id)
);

-- ─── 7. teacher_timetable_slots ──────────────────────────────────────────────
-- Weekly recurring slot assignments (day + period → subject class).

CREATE TABLE IF NOT EXISTS teacher_timetable_slots (
    id                    UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    teacher_timetable_id  UUID NOT NULL REFERENCES teacher_timetables(id) ON DELETE CASCADE,
    profile_id            UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    institute_id          UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    day_of_week           TEXT NOT NULL,
    period_code           TEXT NOT NULL,
    subject_class         TEXT NOT NULL,
    start_time            TEXT NOT NULL,
    end_time              TEXT NOT NULL,
    neo4j_node_id         TEXT,
    created_at            TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at            TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (teacher_timetable_id, day_of_week, period_code)
);

-- ============================================================
-- Indexes
-- ============================================================

CREATE INDEX IF NOT EXISTS idx_school_tt_institute     ON school_timetables(institute_id);
CREATE INDEX IF NOT EXISTS idx_academic_years_tt       ON academic_years(school_timetable_id);
CREATE INDEX IF NOT EXISTS idx_academic_years_inst     ON academic_years(institute_id);
CREATE INDEX IF NOT EXISTS idx_academic_terms_year     ON academic_terms(academic_year_id);
CREATE INDEX IF NOT EXISTS idx_academic_terms_inst     ON academic_terms(institute_id);
CREATE INDEX IF NOT EXISTS idx_academic_weeks_term     ON academic_weeks(academic_term_id);
CREATE INDEX IF NOT EXISTS idx_academic_weeks_inst     ON academic_weeks(institute_id);
CREATE INDEX IF NOT EXISTS idx_academic_days_week      ON academic_days(academic_week_id);
CREATE INDEX IF NOT EXISTS idx_academic_days_term      ON academic_days(academic_term_id);
CREATE INDEX IF NOT EXISTS idx_academic_days_inst_date ON academic_days(institute_id, date);
CREATE INDEX IF NOT EXISTS idx_teacher_tt_profile      ON teacher_timetables(profile_id);
CREATE INDEX IF NOT EXISTS idx_teacher_tt_inst         ON teacher_timetables(institute_id);
CREATE INDEX IF NOT EXISTS idx_tt_slots_timetable      ON teacher_timetable_slots(teacher_timetable_id);
CREATE INDEX IF NOT EXISTS idx_tt_slots_profile        ON teacher_timetable_slots(profile_id);

-- ============================================================
-- updated_at triggers (tables that have updated_at)
-- ============================================================

DO $$ DECLARE t TEXT; BEGIN
    FOREACH t IN ARRAY ARRAY[
        'school_timetables', 'teacher_timetables', 'teacher_timetable_slots'
    ] LOOP
        EXECUTE format(
            'DROP TRIGGER IF EXISTS trg_updated_at ON %I;
             CREATE TRIGGER trg_updated_at
             BEFORE UPDATE ON %I
             FOR EACH ROW EXECUTE FUNCTION set_updated_at();',
            t, t
        );
    END LOOP;
END $$;

-- ============================================================
-- Row Level Security
-- ============================================================

ALTER TABLE school_timetables       ENABLE ROW LEVEL SECURITY;
ALTER TABLE academic_years          ENABLE ROW LEVEL SECURITY;
ALTER TABLE academic_terms          ENABLE ROW LEVEL SECURITY;
ALTER TABLE academic_weeks          ENABLE ROW LEVEL SECURITY;
ALTER TABLE academic_days           ENABLE ROW LEVEL SECURITY;
ALTER TABLE teacher_timetables      ENABLE ROW LEVEL SECURITY;
ALTER TABLE teacher_timetable_slots ENABLE ROW LEVEL SECURITY;

-- school_timetables: institute members can read
CREATE POLICY "stt_inst_read" ON school_timetables FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "stt_service" ON school_timetables FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- academic_years
CREATE POLICY "ay_inst_read" ON academic_years FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "ay_service" ON academic_years FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- academic_terms
CREATE POLICY "at_inst_read" ON academic_terms FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "at_service" ON academic_terms FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- academic_weeks
CREATE POLICY "aw_inst_read" ON academic_weeks FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "aw_service" ON academic_weeks FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- academic_days
CREATE POLICY "ad_inst_read" ON academic_days FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "ad_service" ON academic_days FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- teacher_timetables: own row
CREATE POLICY "tcht_own_read" ON teacher_timetables FOR SELECT
    USING (profile_id = auth.uid());
CREATE POLICY "tcht_service" ON teacher_timetables FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- teacher_timetable_slots: own rows
CREATE POLICY "tchts_own_read" ON teacher_timetable_slots FOR SELECT
    USING (profile_id = auth.uid());
CREATE POLICY "tchts_service" ON teacher_timetable_slots FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');
