-- Denormalized manga -> content-warning set.
--
-- The list filter previously tested each candidate manga with a correlated
-- `page_content_warnings -> pages -> chapters` join, i.e. O(mangas * pages) on
-- every filtered list AND its count. This table holds the DISTINCT union of a
-- manga's page warnings so the filter is a single indexed lookup.
--
-- Kept in sync by triggers that recompute an affected manga's set from current
-- data (the set is tiny — at most the five moderation labels — so a
-- delete-and-reinsert per change is cheap and always correct, sidestepping the
-- cascade-ordering hazards of incremental maintenance).

CREATE TABLE manga_content_warnings (
    manga_id uuid NOT NULL REFERENCES mangas(id) ON DELETE CASCADE,
    warning  text NOT NULL
                CHECK (warning IN ('sexual', 'nudity', 'gore', 'violence', 'disturbing')),
    PRIMARY KEY (manga_id, warning)
);

-- warning -> mangas, for the include/exclude list filters.
CREATE INDEX manga_content_warnings_warning_idx ON manga_content_warnings (warning);

-- Recompute a single manga's warning set from the live per-page rows.
CREATE OR REPLACE FUNCTION mcw_refresh_for_manga(mid uuid) RETURNS void AS $$
BEGIN
    -- Skip when the manga is gone (e.g. mid-cascade of a manga delete) so we
    -- never re-insert a row that would violate the FK / resurrect a deleted set.
    IF NOT EXISTS (SELECT 1 FROM mangas WHERE id = mid) THEN
        RETURN;
    END IF;
    DELETE FROM manga_content_warnings WHERE manga_id = mid;
    INSERT INTO manga_content_warnings (manga_id, warning)
    SELECT DISTINCT mid, pw.warning
      FROM page_content_warnings pw
      JOIN pages p    ON p.id = pw.page_id
      JOIN chapters c ON c.id = p.chapter_id
     WHERE c.manga_id = mid;
END;
$$ LANGUAGE plpgsql;

-- page_content_warnings changed for a page: refresh that page's manga.
CREATE OR REPLACE FUNCTION mcw_on_pcw_change() RETURNS trigger AS $$
DECLARE
    mid uuid;
    pid uuid := COALESCE(NEW.page_id, OLD.page_id);
BEGIN
    -- The page (and thus chapter) may already be gone when this fires as part
    -- of a pages/chapters cascade; in that case the pages/chapters triggers do
    -- the refresh instead, so a missing join here is harmless.
    SELECT c.manga_id INTO mid
      FROM pages p JOIN chapters c ON c.id = p.chapter_id
     WHERE p.id = pid;
    IF mid IS NOT NULL THEN
        PERFORM mcw_refresh_for_manga(mid);
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER mcw_pcw_ins AFTER INSERT ON page_content_warnings
    FOR EACH ROW EXECUTE FUNCTION mcw_on_pcw_change();
CREATE TRIGGER mcw_pcw_del AFTER DELETE ON page_content_warnings
    FOR EACH ROW EXECUTE FUNCTION mcw_on_pcw_change();

-- A page was deleted (directly, or via a chapter cascade): its
-- page_content_warnings rows are already cascade-gone, so recompute from the
-- chapter's manga. If the chapter is gone too, the chapters trigger covers it.
CREATE OR REPLACE FUNCTION mcw_on_page_delete() RETURNS trigger AS $$
DECLARE
    mid uuid;
BEGIN
    SELECT c.manga_id INTO mid FROM chapters c WHERE c.id = OLD.chapter_id;
    IF mid IS NOT NULL THEN
        PERFORM mcw_refresh_for_manga(mid);
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER mcw_page_del AFTER DELETE ON pages
    FOR EACH ROW EXECUTE FUNCTION mcw_on_page_delete();

-- A chapter was deleted (directly, or via a manga cascade): recompute from the
-- chapter's manga. `chapters.manga_id` is on the row, so it is always available
-- even after the child pages have cascaded; the refresh no-ops when the manga
-- itself is being deleted.
CREATE OR REPLACE FUNCTION mcw_on_chapter_delete() RETURNS trigger AS $$
BEGIN
    PERFORM mcw_refresh_for_manga(OLD.manga_id);
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER mcw_chapter_del AFTER DELETE ON chapters
    FOR EACH ROW EXECUTE FUNCTION mcw_on_chapter_delete();

-- Backfill from existing per-page rows.
INSERT INTO manga_content_warnings (manga_id, warning)
SELECT DISTINCT c.manga_id, pw.warning
  FROM page_content_warnings pw
  JOIN pages p    ON p.id = pw.page_id
  JOIN chapters c ON c.id = p.chapter_id
ON CONFLICT DO NOTHING;
