// dependency-relaxation-proposals (roadmap Sprint 3).
//
// Two small pieces that sit beside the admission decisions, because they answer the
// same operator question — "why is the board serialised, and what can I do about it?":
//
//   • CeilingsLine — the two REAL ceilings on parallelism (limits.maxConcurrentAgents and
//     lineManager.wipLimit, names as in runtime.yaml) with current usage, read from the
//     snapshot's meta.ceilings (assembly-line-runtime/conductor/producer.ts). A board
//     that looks stuck at two tasks is explainable without opening the config file.
//
//   • PlanRelaxationsPanel — the orchestrator's (or an operator's) proposals to drop one
//     dependsOn edge, each a REVIEWABLE PLAN REVISION. Nothing changes readiness until the
//     operator approves here (or via bin/al-plan-relax): Layer 1 reads plan.json, never
//     the proposals. Approve/reject go to the conductor's create API through the viewer's
//     /api/create proxy (operator-gated; POST carries the origin guard).
//
// Self-contained fetches rather than lib/api.js helpers: the routes are two URLs and the
// panel is the only consumer.
const { useState: useStatePR, useEffect: useEffectPR } = React;

const CeilingsLine = ({ ceilings }) => {
  if (!ceilings) return null;
  const agentsFull = ceilings.agentsInUse != null && ceilings.agentsInUse >= ceilings.maxConcurrentAgents;
  const wipFull = ceilings.wip != null && ceilings.wip >= ceilings.wipLimit;
  const chip = (full) => `px-1.5 py-0.5 rounded font-mono ${full ? 'bg-amber-100 text-amber-800' : 'bg-neutral-100 text-neutral-700'}`;
  return (
    <div
      className="text-xs text-neutral-600 flex items-center gap-2"
      title="The two ceilings that bound parallelism: limits.maxConcurrentAgents (agent slots held / limit) and lineManager.wipLimit (features in flight / limit). Amber = full — the line is serialised by the ceiling, not by dependencies."
    >
      <span className="font-bold text-neutral-500">Ceilings</span>
      <span className={chip(agentsFull)}>agents {ceilings.agentsInUse ?? '?'}/{ceilings.maxConcurrentAgents}</span>
      <span className={chip(wipFull)}>wip {ceilings.wip ?? '?'}/{ceilings.wipLimit}</span>
    </div>
  );
};

const PlanRelaxationsPanel = ({ alProjectId }) => {
  const [proposals, setProposals] = useStatePR([]);
  const [error, setError] = useStatePR(null);
  const [busy, setBusy] = useStatePR(null);

  const base = alProjectId != null ? `/api/create/projects/${encodeURIComponent(alProjectId)}/plan-relaxations` : null;

  const load = async () => {
    if (!base) return;
    setError(null);
    try {
      const res = await fetch(base);
      if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
      const data = await res.json();
      setProposals(Array.isArray(data?.proposals) ? data.proposals : []);
    } catch (err) {
      setError(`Unable to load relaxation proposals (${err.message}).`);
      setProposals([]);
    }
  };

  useEffectPR(() => { load(); }, [base]);

  const decide = async (proposal, decision) => {
    setBusy(proposal.id);
    setError(null);
    try {
      const res = await fetch(`${base}/${encodeURIComponent(proposal.id)}/decision`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ decision }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data?.error || `${res.status} ${res.statusText}`);
      await load();
    } catch (err) {
      setError(`Could not ${decision === 'approved' ? 'approve' : 'reject'} ${proposal.id}: ${err.message}`);
    } finally {
      setBusy(null);
    }
  };

  if (!base) return null;
  const pending = proposals.filter((p) => p.status === 'pending');
  const decided = proposals.filter((p) => p.status !== 'pending');

  const card = (proposal) => (
    <li key={proposal.id} className="border border-neutral-200 rounded-lg p-3 bg-white">
      <div className="flex items-start justify-between gap-3">
        <span className="text-xs font-mono text-neutral-800">
          {proposal.edge.task} <span className="text-neutral-400">⇸</span> {proposal.edge.dependsOn}
        </span>
        <span className={`px-2 py-0.5 rounded-full text-[11px] font-bold ${
          proposal.status === 'pending' ? 'bg-indigo-100 text-indigo-800'
            : proposal.status === 'approved' ? 'bg-emerald-100 text-emerald-800'
              : 'bg-neutral-200 text-neutral-700'}`}>
          {proposal.status}
        </span>
      </div>
      <div className="mt-2 text-xs text-neutral-700"><span className="font-bold text-neutral-500">Evidence:</span> {proposal.evidence}</div>
      <div className="mt-1 text-xs text-neutral-700"><span className="font-bold text-neutral-500">Risk:</span> {proposal.risk}</div>
      <div className="mt-1 text-[11px] font-mono text-neutral-400">
        #{proposal.id} · by {proposal.proposedBy} · {proposal.proposedAt}
        {proposal.decidedAt ? ` · ${proposal.status} by ${proposal.decidedBy} at ${proposal.decidedAt}` : ''}
        {proposal.appliedVersion ? ` · plan v${proposal.appliedVersion}` : ''}
      </div>
      {proposal.status === 'pending' && (
        <div className="mt-2 flex gap-2">
          <button
            type="button"
            disabled={busy === proposal.id}
            onClick={() => decide(proposal, 'approved')}
            className="px-2 py-1 rounded bg-emerald-600 text-white text-xs font-semibold hover:bg-emerald-700 disabled:opacity-50"
          >
            Approve — drop the edge
          </button>
          <button
            type="button"
            disabled={busy === proposal.id}
            onClick={() => decide(proposal, 'rejected')}
            className="px-2 py-1 rounded bg-white border border-neutral-300 text-neutral-700 text-xs font-semibold hover:bg-neutral-50 disabled:opacity-50"
          >
            Reject
          </button>
        </div>
      )}
    </li>
  );

  return (
    <section aria-labelledby="plan-relaxations-title" className="mt-4 border-t border-neutral-200 pt-4">
      <div className="flex items-center justify-between gap-3">
        <h3 id="plan-relaxations-title" className="text-sm font-bold text-neutral-800">Dependency relaxation proposals</h3>
        <button type="button" onClick={load} className="text-xs text-indigo-700 hover:underline">Refresh</button>
      </div>
      <p className="mt-1 text-[11px] text-neutral-500">
        Plan revisions, not overrides: a pending proposal changes nothing. Approving rewrites the plan's dependsOn and takes effect on the next admission pass.
      </p>
      {error && <div role="alert" className="mt-2 text-xs text-red-700 bg-red-50 border border-red-200 rounded p-2">{error}</div>}
      {!error && proposals.length === 0 && (
        <div role="status" className="mt-2 text-xs text-neutral-500 italic">No relaxation proposals for this project.</div>
      )}
      {pending.length > 0 && <ol className="mt-2 flex flex-col gap-2">{pending.map(card)}</ol>}
      {decided.length > 0 && (
        <details className="mt-2">
          <summary className="text-xs text-neutral-500 cursor-pointer">{decided.length} decided</summary>
          <ol className="mt-2 flex flex-col gap-2">{decided.map(card)}</ol>
        </details>
      )}
    </section>
  );
};

window.CeilingsLine = CeilingsLine;
window.PlanRelaxationsPanel = PlanRelaxationsPanel;
