/* approval-run.jsx — plain JS, no JSX */
(function () {
  "use strict";

  const sb = window.sb;               // Supabase client, may be null
  const cdeData = window.cdeData;     // { projectId(), logAudit(ev), members() }

  /* ------------------------------------------------------------------ */
  /* 1) instances()                                                      */
  /* ------------------------------------------------------------------ */
  async function instances() {
    if (!sb) return [];
    try {
      const pid = await cdeData.projectId();

      // fetch instances
      const { data: rows, error: errI } = await sb
        .from("approval_instances")
        .select("*")
        .eq("project_id", pid)
        .order("updated_at", { ascending: false });

      if (errI) {
        console.warn("approval-run instances fetch error", errI);
        return [];
      }
      if (!rows || rows.length === 0) return [];

      // fetch containers for this project
      const { data: containers, error: errC } = await sb
        .from("containers")
        .select("id, name, disc")
        .eq("project_id", pid);

      if (errC) {
        console.warn("approval-run containers fetch error", errC);
        return [];
      }
      const containerMap = {};
      if (containers) {
        for (const c of containers) {
          containerMap[c.id] = c;
        }
      }

      // fetch workflows for this project
      const { data: workflows, error: errW } = await sb
        .from("approval_workflows")
        .select("id, name, config")
        .eq("project_id", pid);

      if (errW) {
        console.warn("approval-run workflows fetch error", errW);
        return [];
      }
      const workflowMap = {};
      if (workflows) {
        for (const w of workflows) {
          workflowMap[w.id] = w;
        }
      }

      // build result
      const result = rows.map((row) => {
        const container = containerMap[row.container_id] || {};
        const workflow = workflowMap[row.workflow_id] || {};
        return {
          id: row.id,
          containerId: row.container_id,
          docName: container.name || "",
          disc: container.disc || "",
          workflowId: row.workflow_id,
          wfName: workflow.name || "",
          config: workflow.config || {},
          currentNode: row.current_node,
          status: row.status,
          startedBy: row.started_by,
          startedAt: row.started_at,
          history: row.history || [],
        };
      });

      return result;
    } catch (e) {
      console.warn("approval-run instances error", e);
      return [];
    }
  }

  /* ------------------------------------------------------------------ */
  /* 2) submit(containerId, workflowId)                                   */
  /* ------------------------------------------------------------------ */
  async function submit(containerId, workflowId) {
    if (!sb) throw new Error("Supabase not available");

    const pid = await cdeData.projectId();

    // fetch workflow config
    const { data: wf, error: errW } = await sb
      .from("approval_workflows")
      .select("config")
      .eq("id", workflowId)
      .maybeSingle();

    if (errW) throw errW;
    if (!wf) throw new Error("Workflow not found");

    const config = wf.config || {};
    const nodes = config.nodes || [];

    // determine start node
    const startNode = nodes.find((n) => n.isStart) || nodes[0];
    const current = startNode ? startNode.key : null;

    // insert instance
    const { data, error } = await sb
      .from("approval_instances")
      .insert({
        project_id: pid,
        container_id: containerId,
        workflow_id: workflowId,
        current_node: current,
        status: "running",
        history: [],
        started_by: (window.cdeActorName || "—"),
      })
      .select()
      .maybeSingle();

    if (error) throw error;

    // audit
    try {
      await cdeData.logAudit({
        project_id: pid,
        action: "approval_submit",
        target_type: "container",
        target_id: containerId,
      });
    } catch (_) {
      // non‑fatal
    }

    return data;
  }

  /* ------------------------------------------------------------------ */
  /* applyEffects — chạy hành động của cổng theo kết quả đã chọn          */
  /*   prop  : đổi giá trị thuộc tính tài liệu                            */
  /*   copy  : tạo bản sao ở thư mục đích, giữ source_id trỏ về bản gốc   */
  /*   delete: xoá tài liệu (cascade luôn xoá phiên duyệt của nó)         */
  /* ------------------------------------------------------------------ */
  async function applyEffects(config, node, outcomeKey, containerId) {
    if (!sb || !node) return;
    const gate = (config.gates || []).find((g) => g.id === node.gateId);
    const list = (gate && gate.effects && gate.effects[outcomeKey]) || [];
    if (!list.length) return;

    // tài liệu gốc (cho prop/copy)
    const { data: src } = await sb
      .from("containers").select("*").eq("id", containerId).maybeSingle();
    if (!src) return;

    for (const ef of list) {
      const type = ef.type || "prop";
      try {
        if (type === "prop" && ef.prop) {
          const props = Object.assign({}, src.props || {});
          props[ef.prop] = ef.value;
          await sb.from("containers")
            .update({ props: props, updated_at: new Date().toISOString() })
            .eq("id", containerId);
          src.props = props;
        } else if (type === "copy") {
          const copy = {
            project_id: src.project_id,
            name: src.name + " (bản sao)",
            type: src.type, disc: src.disc, status: src.status,
            rev: src.rev, suitability: src.suitability,
            size_bytes: src.size_bytes, descr: src.descr,
            props: src.props || {},
            source_id: src.id,            // giữ liên kết tới bản gốc
            date_label: src.date_label,
          };
          const { data: newC } = await sb.from("containers").insert(copy).select().maybeSingle();
          // bản sao trỏ CÙNG object R2 của bản gốc → tải về được ngay (không nhân đôi bytes)
          if (newC) {
            const { data: ov } = await sb.from("versions")
              .select("*").eq("container_id", src.id).limit(1).maybeSingle();
            if (ov) await sb.from("versions").insert({
              container_id: newC.id, rev: ov.rev, status: ov.status,
              note: "Bản sao từ " + src.name, r2_key: ov.r2_key,
              size_bytes: ov.size_bytes, date_label: ov.date_label,
            });
          }
        } else if (type === "delete") {
          // XOÁ MỀM — đặt deleted_at, ẩn khỏi danh sách, khôi phục được
          await sb.from("containers")
            .update({ deleted_at: new Date().toISOString() }).eq("id", containerId);
        }
      } catch (e) {
        console.warn("applyEffects error", type, e);
      }
    }
  }

  /* ------------------------------------------------------------------ */
  /* 3) advance(instanceId, outcomeKey, by)                               */
  /* ------------------------------------------------------------------ */
  async function advance(instanceId, outcomeKey, by) {
    if (!sb) throw new Error("Supabase not available");

    // fetch instance
    const { data: inst, error: errI } = await sb
      .from("approval_instances")
      .select("*, approval_workflows!inner(config)")
      .eq("id", instanceId)
      .maybeSingle();

    if (errI) throw errI;
    if (!inst) throw new Error("Instance not found");

    const config = inst.approval_workflows ? inst.approval_workflows.config : {};
    const nodes = config.nodes || [];

    // find current node
    const node = nodes.find((n) => n.key === inst.current_node);
    const nextKey = node && node.next ? node.next[outcomeKey] : null;

    // GUARD vai trò: chỉ user mang vai trò của cổng (hoặc admin) mới được duyệt
    try {
      const gate = (config.gates || []).find((g) => g.id === (node && node.gateId));
      const gr = (gate && gate.roles) || [];
      const { data: u } = await sb.auth.getUser();
      const uid = u && u.user && u.user.id;
      const { data: prof } = await sb.from("profiles").select("role").eq("id", uid).maybeSingle();
      const acct = prof && prof.role;
      if (acct !== "admin" && gr.length) {
        const { data: mr } = await sb.from("member_roles").select("role_id").eq("user_id", uid);
        const mine = new Set((mr || []).map((x) => x.role_id));
        if (!gr.some((id) => mine.has(id))) throw new Error("Bạn không có vai trò được phép duyệt cổng này.");
      }
    } catch (e) {
      if (e && e.message && e.message.indexOf("vai trò") >= 0) throw e;   // chỉ ném lỗi quyền, lỗi mạng thì bỏ qua guard
    }

    // build history entry
    const newHistory = [
      ...(inst.history || []),
      {
        node: inst.current_node,
        outcome: outcomeKey,
        by: by || "Trần Đức",
        at: new Date().toISOString(),
      },
    ];

    let status;
    let current_node;

    if (nextKey === "END" || !nextKey) {
      // terminal
      const lower = (outcomeKey || "").toLowerCase();
      const isReject =
        lower.includes("reject") ||
        lower.includes("từ chối") ||
        lower.includes("fail") ||
        lower.includes("không");
      status = isReject ? "rejected" : "approved";
      current_node = null;
    } else {
      status = "running";
      current_node = nextKey;
    }

    // update instance
    const { data, error } = await sb
      .from("approval_instances")
      .update({
        current_node: current_node,
        status: status,
        history: newHistory,
        updated_at: new Date().toISOString(),
      })
      .eq("id", instanceId)
      .select()
      .maybeSingle();

    if (error) throw error;

    // chạy hành động của cổng theo kết quả (sau khi đã cập nhật phiên & giữ `data`,
    // vì effect "delete" sẽ cascade xoá luôn phiên này)
    try {
      await applyEffects(config, node, outcomeKey, inst.container_id);
    } catch (_) {
      // non‑fatal
    }

    // audit
    try {
      await cdeData.logAudit({
        project_id: (data && data.project_id) || undefined,
        action: "approval_advance",
        target_id: instanceId,
        meta: { outcome: outcomeKey },
      });
    } catch (_) {
      // non‑fatal
    }

    return data;
  }

  /* ------------------------------------------------------------------ */
  /* 4) cancel(instanceId) — thu hồi tài liệu khỏi luồng (đặt cancelled)  */
  /* ------------------------------------------------------------------ */
  async function cancel(instanceId, by) {
    if (!sb) throw new Error("Supabase not available");
    const { data, error } = await sb
      .from("approval_instances")
      .update({ status: "cancelled", current_node: null, updated_at: new Date().toISOString() })
      .eq("id", instanceId)
      .select()
      .maybeSingle();
    if (error) throw error;
    try {
      await cdeData.logAudit({ action: "approval_cancel", target_id: instanceId, meta: { by: by || "" } });
    } catch (_) { /* non-fatal */ }
    return data;
  }

  /* ------------------------------------------------------------------ */
  /* export                                                              */
  /* ------------------------------------------------------------------ */
  window.cdeApprovalRun = {
    instances: instances,
    submit: submit,
    advance: advance,
    cancel: cancel,
  };
})();
