-- Custom migration, hand-authored. Drizzle cannot diff a SQL function.
--
-- Fixed by #14 decision 1: ONE function collapses the four inputs #12 named
-- into a release level, and ONE lookup answers whether a class is open at it.
-- Nothing else in the system encodes lifecycle.
--
-- ── WHY plpgsql AND WHY FIRST ────────────────────────────────────────────
-- The RLS policies are emitted by Drizzle in the same migration as the tables
-- they guard, so the functions those policies call must already exist. But the
-- functions read `solicitation`, `participation` and `release_matrix`, which
-- that same later migration creates.
--
-- `LANGUAGE sql` validates the body at CREATE time and would fail here.
-- plpgsql defers validation to first call, which is exactly the property
-- needed to break the cycle. Signatures use `text` rather than the
-- `release_level` enum for the same reason — the enum does not exist yet.
-- ─────────────────────────────────────────────────────────────────────────

-- The four inputs: audience membership, stage, participation state, acknowledgement.
CREATE FUNCTION release_level_for_solicitation(p_solicitation uuid)
RETURNS text
LANGUAGE plpgsql STABLE AS $$
DECLARE
  v_level text;
BEGIN
  SELECT CASE
    WHEN p.id IS NULL           THEN 'none'
    WHEN s.stage = 'awarded'    THEN 'awarded'
    WHEN s.stage = 'finalists'  THEN 'finalist'
    WHEN s.stage = 'evaluating' THEN 'evaluating'
    -- The deadline is the moment the whole field opens at once (#14 decision 2).
    -- Before it, no response content is readable by anyone but its author, which
    -- is what makes bid shopping structurally impossible rather than discouraged.
    WHEN s.responses_due_at IS NOT NULL AND now() >= s.responses_due_at THEN 'open'
    WHEN p.state = 'submitted'  THEN 'submitted'
    WHEN p.acknowledged_at IS NOT NULL OR NOT s.acknowledgement_required
                                THEN 'acknowledged'
    ELSE 'invited'
  END
  INTO v_level
  FROM solicitation s
  LEFT JOIN participation p
    ON p.solicitation_id = s.id
   AND p.vendor_org_id = nullif(current_setting('app.org_id', true), '')::uuid
  WHERE s.id = p_solicitation;

  RETURN coalesce(v_level, 'none');
END;
$$;

-- The issuer reaches their own event at the level its stage implies, without
-- being a participant in it. Everyone else gets 'none' — including a dual-role
-- org's issuing side looking at its own vendor side (#11 decision 2).
CREATE FUNCTION release_level_for_response(p_response uuid)
RETURNS text
LANGUAGE plpgsql STABLE AS $$
DECLARE
  v_level text;
BEGIN
  SELECT CASE
    WHEN current_setting('app.capability', true) = 'issuer'
     AND s.issuer_org_id = nullif(current_setting('app.org_id', true), '')::uuid
      THEN release_level_for_solicitation(s.id)
    WHEN current_setting('app.capability', true) = 'vendor'
     AND r.vendor_org_id = nullif(current_setting('app.org_id', true), '')::uuid
      THEN release_level_for_solicitation(s.id)
    ELSE 'none'
  END
  INTO v_level
  FROM response r
  JOIN solicitation s ON s.id = r.solicitation_id
  WHERE r.id = p_response;

  RETURN coalesce(v_level, 'none');
END;
$$;

-- Reads the CURRENT matrix version. A past release is reconstructed by reading
-- an older version, which is why the matrix is versioned rather than updated.
CREATE FUNCTION class_released(p_level text, p_class text)
RETURNS boolean
LANGUAGE plpgsql STABLE AS $$
DECLARE
  v_released boolean;
BEGIN
  SELECT released = 'true'
    INTO v_released
    FROM release_matrix
   WHERE class = p_class::disclosure_class
     AND level = p_level::release_level
   ORDER BY version DESC
   LIMIT 1;

  RETURN coalesce(v_released, false);
END;
$$;
