-- Dedup analyze_page jobs in flight (mirrors 0014 for sync_chapter_content).
--
-- Without this, `repo::page_analysis::enqueue_pages`' `NOT EXISTS(... pending
-- | running ...)` read-then-insert race let concurrent admin POSTs land
-- duplicate jobs for the same page — visible as duplicate "now analyzing"
-- SSE events and inflated `pending` counters in the admin dashboard, even
-- though the worker's `skip-if-done` net (analysis/daemon.rs:210-217)
-- prevented the actual duplicate vision call.
--
-- The partial unique index lets enqueue_pages drop the NOT EXISTS subquery
-- and rely on `ON CONFLICT DO NOTHING` instead. At most one (pending|running)
-- job per page_id can exist; the slot frees the moment the job transitions
-- to done/failed/dead, so a force re-analyze still re-enqueues cleanly.

-- **Pre-dedup step.** Production runs of the prior buggy producer can
-- have left duplicate rows tied on (kind='analyze_page', payload->>'page_id',
-- state IN ('pending','running')). Adding the UNIQUE INDEX onto a dirty
-- table would fail to create and crash `sqlx::migrate!` at boot.
--
-- Demote all-but-the-lowest-id duplicates to `dead` first. Why dead and
-- not done: the dropped sibling never actually ran, and `dead` is the
-- terminal state operators inspect via the dead-jobs requeue endpoint —
-- so a curator can recover any genuinely-needed page. The lowest-id row
-- is the one a worker most likely already leased / has heartbeat'd, so
-- keeping it minimises lease-state thrash.
UPDATE crawler_jobs
   SET state = 'dead',
       last_error = COALESCE(last_error, '') ||
                    CASE WHEN last_error IS NULL OR last_error = '' THEN ''
                         ELSE ' | ' END ||
                    'pre-0031 duplicate analyze_page; superseded by earlier sibling',
       updated_at = now()
 WHERE id IN (
    SELECT id FROM (
        SELECT id,
               row_number() OVER (
                   PARTITION BY payload->>'page_id'
                   ORDER BY id
               ) AS rn
          FROM crawler_jobs
         WHERE payload->>'kind' = 'analyze_page'
           AND state IN ('pending', 'running')
    ) ranked
    WHERE rn > 1
 );

CREATE UNIQUE INDEX crawler_jobs_analyze_page_dedup_idx
    ON crawler_jobs ((payload->>'page_id'))
 WHERE state IN ('pending', 'running')
   AND payload->>'kind' = 'analyze_page';
