/* ELEV8 ATS - Submissions: cross-job submittal pipeline.
   Stage model (Submitted → Client review → Interview → Offer → Placed) is
   data-driven from E8DATA.submissionStages - never hardcoded. */

const DSs = window.Stand8DesignSystem_b5c975;

/* ---- R104 T3: live submission pipeline. A stage move is a real, reversible op: each helper writes
   through E8Store.set (replayable + survives reload), appends a real {stage, at, by} entry to the
   submission's stageHistory, logs to audit, and returns an { undo } closure so the caller shows a
   single Undo toast that restores the pre-move snapshot. Reaching the terminal Placed stage still
   routes through the T1 create-engagement executor (the Place flow) - advanceSubmission never writes
   Placed itself, so the candidate -> engagement -> consultant identity spine is never bypassed. ---- */
function e8SubActor() {
  const D = window.E8DATA || {};
  return (D.user && D.user.name) || 'You';
}
/* A compact date for interview/offer chips on the row (e.g. "Jul 17"). */
function e8SubFmtDate(ts) {
  if (!ts) return '';
  try { return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } catch (e) { return ''; }
}
/* Snapshot the fields a move can touch, coercing absent object keys to null so the undo op survives a
   JSON round-trip + replay (undefined keys are dropped by JSON.stringify, mirroring the T1 place undo). */
function e8SubSnapshot(sub) {
  return {
    stage: sub.stage, when: sub.when, resp: sub.resp, open: sub.open,
    stageHistory: Array.isArray(sub.stageHistory) ? sub.stageHistory : null,
    interview: sub.interview || null, offer: sub.offer || null,
  };
}
/* The submission's history, or a synthesized opening entry (older seed / queue rows may lack one) so a
   first move still records a measurable delta. */
function e8SubHistory(sub) {
  return (Array.isArray(sub.stageHistory) && sub.stageHistory.length)
    ? sub.stageHistory
    : [{ stage: sub.stage | 0, at: (window.e8WhenToTs ? window.e8WhenToTs(sub.when) : Date.now()), by: sub.owner || e8SubActor() }];
}
function e8SubRespFor(stageIdx, stages) {
  const label = stages[stageIdx] || 'Submitted';
  if (label === 'Interview') return { label: 'In interview', tone: 'info' };
  if (label === 'Offer') return { label: 'Offer stage', tone: 'success' };
  if (label === 'Client review') return { label: 'Awaiting feedback', tone: 'neutral' };
  return { label: 'Moved to ' + label, tone: 'neutral' };
}

/* Advance to the next stage (or an explicit toStage). Guards: a closed/placed/terminal submission
   can't move; no backward moves; and it never writes the final Placed stage (that is the Place flow). */
window.e8AdvanceSubmission = function (sub, toStage) {
  const D = window.E8DATA || {};
  const stages = D.submissionStages || [];
  const PLACED = stages.length - 1;
  const cur = sub.stage | 0;
  const next = (toStage != null) ? toStage : cur + 1;
  if (!sub.open || sub.placed || cur >= PLACED) return null;   // terminal - nothing to advance
  if (next <= cur || next >= PLACED) return null;              // no backward move, never into Placed
  const before = e8SubSnapshot(sub);
  const hist = e8SubHistory(sub).concat([{ stage: next, at: Date.now(), by: e8SubActor() }]);
  window.E8Store.set('submissions', sub.id, { stage: next, stageHistory: hist, resp: e8SubRespFor(next, stages), when: 'Just now' });
  if (window.E8Audit) window.E8Audit.log({ agent: 'You', prov: 'human', action: 'Advanced submission to ' + stages[next], target: sub.cand + ' → ' + sub.job, route: 'submissions' });
  return { toStage: next, label: stages[next], undo: function () { window.E8Store.set('submissions', sub.id, before); } };
};

/* Schedule (or reschedule) an interview: stamps the interview sub-object and, when the submission is
   still before the Interview stage, moves it there too (with a real stageHistory entry). */
window.e8ScheduleInterview = function (sub, detail) {
  detail = detail || {};
  const D = window.E8DATA || {};
  const stages = D.submissionStages || [];
  const iInterview = stages.indexOf('Interview');
  const PLACED = stages.length - 1;
  if (!sub.open || sub.placed || (sub.stage | 0) >= PLACED) return null;
  const before = e8SubSnapshot(sub);
  const at = detail.at || (Date.now() + 2 * 864e5);
  const fields = {
    interview: { at: at, mode: detail.mode || 'Video', notes: detail.notes || '' },
    resp: { label: 'Interview scheduled', tone: 'info' }, when: 'Just now',
  };
  if (iInterview > -1 && (sub.stage | 0) < iInterview) {
    fields.stage = iInterview;
    fields.stageHistory = e8SubHistory(sub).concat([{ stage: iInterview, at: Date.now(), by: e8SubActor() }]);
  }
  window.E8Store.set('submissions', sub.id, fields);
  if (window.E8Audit) window.E8Audit.log({ agent: 'You', prov: 'human', action: 'Scheduled interview', target: sub.cand + ' → ' + sub.job, route: 'submissions' });
  return { undo: function () { window.E8Store.set('submissions', sub.id, before); } };
};

/* Withdraw: terminal. A placed submission (it produced an engagement) or an already-closed one can't
   be withdrawn - the guards keep the pipeline honest. */
window.e8WithdrawSubmission = function (sub) {
  if (!sub.open || sub.placed) return null;
  const before = e8SubSnapshot(sub);
  const hist = e8SubHistory(sub).concat([{ stage: sub.stage | 0, at: Date.now(), by: e8SubActor(), withdrawn: true }]);
  window.E8Store.set('submissions', sub.id, { open: false, resp: { label: 'Withdrawn', tone: 'neutral' }, when: 'Just now', stageHistory: hist });
  if (window.E8Audit) window.E8Audit.log({ agent: 'You', prov: 'human', action: 'Withdrew submission', target: sub.cand + ' → ' + sub.job, route: 'submissions' });
  return { undo: function () { window.E8Store.set('submissions', sub.id, before); } };
};

/* R98: New submission - the shared create dialog with a job picker and a fit-sorted candidate
   picker. When a live match-pool row exists for the pair it delegates to e8AcceptDecision (R97)
   so the E8Match decision state stays consistent; otherwise it writes the submission row directly
   (no decision is ever faked). Dedup on candId+jobId blocks a second open submission. */
function NewSubmissionDialog({ onClose, prefillCand, prefillJob }) {
  const D = window.E8DATA;
  const { showToast } = React.useContext(E8Ctx);
  const Dialog = window.E8CreateDialog;
  if (!Dialog) return null;

  const jobs = (D.jobs || []).filter((j) => (j.openings || 1) > (j.filled || 0));
  /* Candidate pool: union of matches profiles and candidates status rows, deduped by id.
     Names live on the matches profile; status-only rows without one are skipped (nothing to show). */
  const seen = {};
  const cands = [];
  (D.matches || []).forEach((m) => { if (m.name && !seen[m.id]) { seen[m.id] = 1; cands.push(m); } });
  (D.candidates || []).forEach((c) => { if (c.name && !seen[c.id]) { seen[c.id] = 1; cands.push(c); } });
  const candById = Object.fromEntries(cands.map((c) => [c.id, c]));

  /* Fit for a candidate against the picked job: stored E8Match / candidateJobs scores when the
     engine knows the person, else the transparent skill-overlap fallback (both via redeployJobs). */
  const fitFor = (c, jobId) => {
    if (!jobId || !window.E8Match) return null;
    try { return window.E8Match.redeployJobs(c.id, c.skills || []).find((e) => e.jobId === jobId) || null; } catch (e) { return null; }
  };

  const dupFor = (vals) => (vals.cand && vals.job)
    ? (D.submissions || []).find((s) => s.candId === vals.cand && s.jobId === vals.job && s.open !== false)
    : null;

  /* R104 T5: a record-level "Submit to job" opens this dialog prefilled to that candidate and their
     best-fit job (from candidateJobs), so the submit CTA is a real, candidate-specific flow rather
     than a fake toast. Only prefill values that are actually pickable (open job / known candidate). */
  const jobInit = (prefillJob && jobs.some((j) => j.id === prefillJob)) ? prefillJob : '';
  const candInit = (prefillCand && candById[prefillCand]) ? prefillCand : '';
  const rateInit = (candInit && candById[candInit] && candById[candInit].rate) ? candById[candInit].rate : '';

  const fields = (vals) => {
    const withFit = cands.map((c) => ({ c, fit: fitFor(c, vals.job) }));
    if (vals.job) withFit.sort((a, b) => ((b.fit ? b.fit.score : -1) - (a.fit ? a.fit.score : -1)) || String(a.c.name).localeCompare(String(b.c.name)));
    const top = withFit.find((x) => x.fit);
    const fitNote = !vals.job ? 'Pick a job order to sort candidates by fit.'
      : !top ? null
      : top.fit.overlap ? ('Sorted by fit - ' + String((top.fit.reasons || [])[0] || 'skill overlap').replace(/^Shares /, '') + ' overlap vs the job requirements')
      : top.fit.live ? 'Sorted by fit - live on-device match scores'
      : 'Sorted by fit - stored match scores';
    return [
      { key: 'job', label: 'Job order', type: 'select', required: true, initial: jobInit, options: [{ value: '', label: 'Select a job order…' }].concat(jobs.map((j) => ({ value: j.id, label: j.id + ' · ' + j.title + ' · ' + j.client }))) },
      { key: 'cand', label: 'Candidate', type: 'select', required: true, initial: candInit, note: fitNote,
        options: [{ value: '', label: 'Select a candidate…' }].concat(withFit.map(({ c, fit }) => ({ value: c.id, label: c.name + (c.title ? ' · ' + c.title : '') + (fit ? ' · fit ' + fit.score : '') }))),
        onPick: (v, { set }) => { const c = candById[v]; if (c && c.rate) set('rate', c.rate); } },
      { key: 'rate', label: 'Bill rate', placeholder: 'e.g. $95/hr', initial: rateInit },
    ];
  };

  return (
    <Dialog
      title="New submission"
      createLabel="Submit"
      createIcon="send"
      onClose={onClose}
      fields={fields}
      disabled={(vals) => !!dupFor(vals)}
      extra={(vals) => {
        const dup = dupFor(vals);
        return dup ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 'var(--ui-text-sm)', color: 'var(--ui-warning-text)' }}>
            <span className="material-symbols-outlined" style={{ fontSize: 15 }}>info</span>
            <span style={{ flex: 1 }}>Already submitted {dup.when === 'Just now' ? 'just now' : dup.when}.</span>
            <DSs.Button variant="secondary" size="sm" onClick={() => { onClose(); navigate('submissions'); }}>Open it</DSs.Button>
          </div>
        ) : null;
      }}
      onCreate={(v) => {
        const j = jobs.find((x) => x.id === v.job);
        const c = candById[v.cand];
        if (!j || !c) return;
        onClose();
        const rate = (v.rate || '').trim();
        const live = (D.matches || []).find((m) => m.id === c.id && (!m.jobId || m.jobId === j.id));
        if (live && window.e8AcceptDecision) {
          /* Real decision path (R97): E8Match decision + deduped submission + audit + bus event. */
          window.e8AcceptDecision(j, rate ? Object.assign({}, live, { rate }) : live);
        } else {
          /* The dedup gate only blocks OPEN pairs - a CLOSED prior submission may still hold
             the base id, so probe a suffix rather than refusing the legitimate resubmit. */
          const base = 's-m-' + c.id + '-' + j.id;
          let sid = base, n = 2;
          while ((D.submissions || []).some((s) => s.id === sid)) sid = base + '-' + n++;
          window.E8Store.add('submissions', {
            id: sid, cand: c.name, candId: c.id, candTitle: (c.title || 'Candidate') + (c.company ? ' · ' + c.company : ''),
            job: j.title, jobId: j.id, client: j.client, rate: rate || c.rate || j.rate || '$-',
            stage: 0, when: 'Just now', owner: D.user.name, resp: { label: 'New', tone: 'neutral' }, open: true,
            stageHistory: [{ stage: 0, at: Date.now(), by: D.user.name }],
          });
          if (window.E8Audit) window.E8Audit.log({ agent: 'You', prov: 'human', action: 'Created submission', target: c.name + ' → ' + j.title, route: 'submissions' });
          if (window.E8Events) window.E8Events.emit('submission:created', { candId: c.id, jobId: j.id });
        }
        showToast('Submitted ' + c.name + ' to ' + j.title, 'Open', () => navigate('submissions'));
      }}
    />
  );
}

function SubmissionsScreen({ tab = 'active' }) {
  const D = window.E8DATA;
  const { showToast, density } = React.useContext(E8Ctx);
  const [insights, setInsights] = React.useState(D.insights.submissions);
  const [sel, setSel] = React.useState([]);
  const [query, setQuery] = React.useState('');
  useBusVersion(); /* R104 T3: re-render on store:changed so a stage move/withdraw/interview updates the rows + bucket counts live */
  const setTab = (t) => { setSel([]); navigate(t === 'active' ? 'submissions' : 'submissions/' + t); };

  /* R104 T3: the live-pipeline actions - each delegates to a shared reversible helper and shows a
     single Undo toast. Advancing from Offer is guarded here: the only forward move is Placed, which is
     the Place flow (create-engagement), so we point the user there rather than faking the terminal move. */
  const stages = D.submissionStages || [];
  const doAdvance = (r) => {
    const res = window.e8AdvanceSubmission ? window.e8AdvanceSubmission(r) : null;
    if (res) { showToast('Advanced ' + r.cand + ' to ' + res.label, 'Undo', res.undo); return; }
    if (r.open && !r.placed && (r.stage | 0) === stages.length - 2) showToast('Ready to place - use Place to create the engagement', 'Place', () => placeSubmission(r));
    else showToast('That submission can’t advance further');
  };
  const doSchedule = (r) => {
    const res = window.e8ScheduleInterview ? window.e8ScheduleInterview(r) : null;
    if (res) showToast('Interview scheduled for ' + r.cand, 'Undo', res.undo);
    else showToast('Can’t schedule an interview on this submission');
  };
  const doWithdraw = (r) => {
    const res = window.e8WithdrawSubmission ? window.e8WithdrawSubmission(r) : null;
    if (res) showToast('Withdrew ' + r.cand + ' from ' + r.job, 'Undo', res.undo);
    else showToast('That submission is already closed or placed');
  };
  /* The advertised 'A' shortcut: a submission row is a focusable tr[role=button], so a keydown from the
     focused row bubbles here. Map it to the row by its position in the (single-page) bucket and advance. */
  const onRowsKeyDown = (e) => {
    if ((e.key === 'a' || e.key === 'A') && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) {
      const tr = e.target;
      if (tr && tr.tagName === 'TR' && tr.getAttribute('role') === 'button' && tr.parentNode) {
        const idx = Array.prototype.indexOf.call(tr.parentNode.children, tr);
        const r = rows[idx];
        if (r) { e.preventDefault(); doAdvance(r); }
      }
    }
  };

  /* R98 T5B: place a submission - open the engagement dialog prefilled from the submission and its
     job order (consultant = the candidate, bill from the job's rate with the submittal rate as a
     fallback, skills from the candidate profile or the job). Creating there marks this submission
     placed and stands up the engagement + consultant + commission attribution. */
  const placeSubmission = (r) => {
    const job = (D.jobs || []).find((j) => j.id === r.jobId);
    const parseRate = (v) => parseFloat(String(v || '').replace(/[^0-9.]/g, '')) || 0;
    const bill = (job && parseRate(job.rate)) || parseRate(r.rate) || undefined;
    const prof = r.candId ? (D.matches || []).find((m) => m.id === r.candId) : null;
    const skills = (prof && prof.skills) || (job && job.skills) || [];
    const prefill = {
      consultantName: r.cand, candId: r.candId, client: r.client, role: r.job,
      bill, pay: undefined, start: undefined, duration: (job && job.duration) || undefined,
      jobId: r.jobId, submissionId: r.id, skills, location: prof && prof.location,
    };
    if (window.e8OpenCreate) window.e8OpenCreate('engagement', { prefill });
    else showToast('Create engagement is unavailable in this build');
  };

  const all = D.submissions;
  const buckets = {
    active: all.filter((s) => s.open),
    interviews: all.filter((s) => s.open && s.stage === 2),
    offers: all.filter((s) => s.open && s.stage === 3),
    closed: all.filter((s) => !s.open),
  };
  const q = query.trim().toLowerCase();
  const rows = (buckets[tab] || buckets.active).filter((s) => !q || Object.values(s).some((v) => typeof v === 'string' && v.toLowerCase().includes(q)));

  return (
    <React.Fragment>
      <Topbar
        crumbs={[{ label: 'Submissions' }]}
        actions={
          <React.Fragment>
            <DSs.InsightsButton
              items={insights.map((i) => ({
                id: i.id,
                text: <span><b>{i.strong}</b>{i.rest}</span>,
                action: { label: i.action, onClick: () => showToast(i.action + ' - queued') },
              }))}
              onDismiss={(id) => setInsights(insights.filter((x) => x.id !== id))}
            />
            <DSs.Button variant="ghost" size="sm" icon="view_column" iconOnly title="Columns"></DSs.Button>
            <DSs.Button variant="secondary" size="sm" icon="upload">Export</DSs.Button>
            <DSs.Button variant="primary" size="sm" icon="send" onClick={() => window.e8OpenCreate && window.e8OpenCreate('submission')}>New submission</DSs.Button>
          </React.Fragment>
        }
      />
      <div className="e8-content">
        <div className="e8-page">
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap', margin: '0 0 14px' }}>
            <DSs.SubTabs
              items={[
                { id: 'active', label: 'Active', count: buckets.active.length },
                { id: 'interviews', label: 'Interviews', count: buckets.interviews.length },
                { id: 'offers', label: 'Offers', count: buckets.offers.length },
                { id: 'closed', label: 'Closed', count: buckets.closed.length },
              ]}
              active={tab}
              onChange={setTab}
            />
            <div style={{ flex: 1, minWidth: 240, maxWidth: 460 }}>
              <DSs.Input icon="search" placeholder="Search submittals - candidate, job, client…" kbd="⌘K" value={query} onChange={(e) => setQuery(e.target ? e.target.value : e)} />
            </div>
          </div>

          <div style={{ position: 'relative' }} onKeyDown={onRowsKeyDown}>
            <ConfigurableTable
              listKey="submissions"
              density={density}
              selectable
              selected={sel}
              onSelect={setSel}
              onRowClick={(r) => r.candId ? navigate('candidate/' + r.candId) : showToast('Candidate record not in demo data')}
              registry={[
                { key: 'cand', label: 'Candidate', locked: true, sortable: true, sortValue: (r) => r.cand, render: (r) => (
                  <NameCell name={r.cand} sub={r.candTitle} size="sm" />
                ) },
                { key: 'job', label: 'Job order', render: (r) => (
                  <span>
                    <span style={{ fontWeight: 500 }}>{r.job}</span>
                    <span style={{ display: 'block', fontSize: 'var(--ui-text-sm)', color: 'var(--ui-text-tertiary)' }}>{r.jobId} · {r.client}</span>
                  </span>
                ) },
                { key: 'stage', label: 'Stage', width: 190, render: (r) => (
                  <DSs.StageCell stages={D.submissionStages} current={r.stage} />
                ) },
                { key: 'resp', label: 'Client response', render: (r) => (
                  <span>
                    <DSs.Badge tone={r.resp.tone} dot={r.resp.tone !== 'neutral'}>{r.resp.label}</DSs.Badge>
                    {r.interview ? <span style={{ display: 'block', fontSize: 'var(--ui-text-xs)', color: 'var(--ui-text-tertiary)', marginTop: 3 }}>Interview {e8SubFmtDate(r.interview.at)}{r.interview.mode ? ' · ' + r.interview.mode : ''}</span> : null}
                    {r.offer ? <span style={{ display: 'block', fontSize: 'var(--ui-text-xs)', color: 'var(--ui-text-tertiary)', marginTop: 3 }}>Offer{r.offer.amount ? ' ' + r.offer.amount : ''} · {r.offer.status}</span> : null}
                  </span>
                ) },
                { key: 'rate', label: 'Bill rate', num: true, sortable: true, sortValue: (r) => parseFloat(String(r.rate).replace(/[^0-9.]/g, '')) || 0 },
                { key: 'when', label: 'Submitted', secondary: true },
                { key: 'owner', label: 'Owner', sortable: true, sortValue: (r) => r.owner, render: (r) => <ActorChip name={r.owner} /> },
              ]}
              rows={rows}
              empty={
                <div style={{ maxWidth: 460, margin: '40px auto' }}>
                  {q
                    ? <DSs.EmptyState icon="search_off" title="No submittals match" body={'Nothing in ' + tab + ' matches “' + query.trim() + '”. Try a different search or clear it.'} cta={<DSs.Button variant="secondary" icon="close" onClick={() => setQuery('')}>Clear search</DSs.Button>} />
                    : <DSs.EmptyState icon="send" title={'No ' + tab + ' submittals'} body="Submittals you send to clients land here. Submit a candidate from any job order to get started." />}
                </div>
              }
              rowActions={(r) => (
                <React.Fragment>
                  {r.open && !r.placed ? (
                    <DSs.Button variant="secondary" size="sm" icon="how_to_reg" onClick={() => placeSubmission(r)}>Place</DSs.Button>
                  ) : null}
                  {r.open && !r.placed ? (
                    <DSs.Button variant="secondary" size="sm" icon="mail" onClick={() => showToast('Nudge drafted - feedback request to ' + r.client, 'Review')}>Nudge</DSs.Button>
                  ) : null}
                  <WorkspacePinButton type="submission" id={r.id} compact />
                  <DSs.MenuButton
                    items={[
                      ...(r.open && !r.placed ? [{ icon: 'how_to_reg', label: 'Create engagement', onClick: () => placeSubmission(r) }, { separator: true }] : []),
                      { icon: 'description', label: 'View submission packet', onClick: () => showToast('Submission packet - ' + r.cand) },
                      /* Live pipeline (R104 T3). Open, non-placed submissions can schedule an interview and
                         advance a stage; Advance stops at Offer (Placed is the Place flow). Placed/closed
                         submissions are terminal - the actions drop off. */
                      ...(r.open && !r.placed ? [
                        { icon: 'event', label: r.interview ? 'Reschedule interview' : 'Schedule interview', onClick: () => doSchedule(r) },
                        ...((r.stage | 0) < stages.length - 2 ? [{ icon: 'forward', label: 'Advance stage', kbd: 'A', onClick: () => doAdvance(r) }] : []),
                      ] : []),
                      ...(r.open && !r.placed ? [{ separator: true }, { icon: 'undo', label: 'Withdraw', danger: true, onClick: () => doWithdraw(r) }] : []),
                    ]}
                  />
                </React.Fragment>
              )}
            />
            <DSs.BulkBar
              count={sel.length}
              actions={[
                { icon: 'mail', label: 'Nudge clients', onClick: () => { showToast('Feedback nudges drafted for ' + sel.length + ' submittals', 'Review'); setSel([]); } },
                { icon: 'upload', label: 'Export packet' },
              ]}
              onClear={() => setSel([])}
            />
          </div>
        </div>
      </div>
    </React.Fragment>
  );
}

Object.assign(window, { SubmissionsScreen, NewSubmissionDialog });
