-- 0002_triggers.sql — universal changelog trigger + LISTEN/NOTIFY channel
-- Используется per-table через core.track_table('schema.table').

-- ---- Universal trigger function ----
CREATE OR REPLACE FUNCTION core.log_change() RETURNS trigger
LANGUAGE plpgsql AS $$
DECLARE
  v_op    CHAR(1);
  v_row   JSONB;
  v_rowid TEXT;
  v_actor TEXT;
  v_id    BIGINT;
  v_table TEXT := TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME;
BEGIN
  -- actor берём из session-level GUC mocl.actor (server выставляет SET LOCAL перед каждым call)
  BEGIN
    v_actor := current_setting('mocl.actor', true);
  EXCEPTION WHEN OTHERS THEN
    v_actor := NULL;
  END;

  IF TG_OP = 'INSERT' THEN
    v_op := 'I'; v_row := to_jsonb(NEW); v_rowid := (v_row->>'id');
  ELSIF TG_OP = 'UPDATE' THEN
    v_op := 'U'; v_row := to_jsonb(NEW); v_rowid := (v_row->>'id');
  ELSIF TG_OP = 'DELETE' THEN
    v_op := 'D'; v_row := to_jsonb(OLD); v_rowid := (v_row->>'id');
  END IF;

  INSERT INTO core.changelog (table_name, op, row_id, row, actor)
  VALUES (v_table, v_op, v_rowid, v_row, v_actor)
  RETURNING id INTO v_id;

  -- NOTIFY доставляется только после COMMIT — подписчики не увидят грязных данных.
  -- Payload компактный: { id, t, op, rid } — клиент при необходимости подтянет полную row.
  PERFORM pg_notify(
    'core_changelog',
    json_build_object('id', v_id, 't', v_table, 'op', v_op, 'rid', v_rowid)::text
  );

  IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END$$;

-- ---- Helper: track_table('schema.table') ----
-- Идемпотентно навешивает AFTER INSERT/UPDATE/DELETE триггер на указанную таблицу
-- и регистрирует её в core.synctable (mode по умолчанию 'cached-full').
CREATE OR REPLACE FUNCTION core.track_table(p_qname TEXT, p_mode TEXT DEFAULT 'cached-full')
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  v_trigger TEXT := 'core_log_' || replace(p_qname, '.', '_');
BEGIN
  EXECUTE format('DROP TRIGGER IF EXISTS %I ON %s', v_trigger, p_qname);
  EXECUTE format(
    'CREATE TRIGGER %I AFTER INSERT OR UPDATE OR DELETE ON %s
     FOR EACH ROW EXECUTE FUNCTION core.log_change()',
    v_trigger, p_qname
  );
  INSERT INTO core.synctable (table_name, mode)
  VALUES (p_qname, p_mode)
  ON CONFLICT (table_name) DO UPDATE SET mode = EXCLUDED.mode;
END$$;

-- ---- Helper: untrack_table ----
CREATE OR REPLACE FUNCTION core.untrack_table(p_qname TEXT) RETURNS void
LANGUAGE plpgsql AS $$
DECLARE v_trigger TEXT := 'core_log_' || replace(p_qname, '.', '_');
BEGIN
  EXECUTE format('DROP TRIGGER IF EXISTS %I ON %s', v_trigger, p_qname);
  DELETE FROM core.synctable WHERE table_name = p_qname;
END$$;
