// local-bids-view.jsx — Ethiopian tender notices harvested from local bid
// portals (AfroTender, 2Merkato), scored against the SuSu eligibility profile.
//
// These are procurement tenders, not funding programs: no award ceiling, no
// consortium, mostly Amharic titles, and a bid bond instead of a grant range.
// They deliberately live outside window.OPPORTUNITIES so they never contaminate
// the capital-surface KPIs on the Overview.

const { useState: useLocalState, useMemo: useLocalMemo } = React;

// Deadlines are stored as plain dates and days-left is computed at render, so
// the board cannot go stale the way the hardcoded OPPORTUNITIES data did.
function bidDaysLeft(bid) {
  if (!bid.deadline) return null;
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  return Math.ceil((new Date(`${bid.deadline}T00:00:00`) - today) / 86_400_000);
}

function bidDeadlineLabel(days) {
  if (days === null) return { label: "No deadline listed", tone: "mute" };
  if (days < 0) return { label: `Closed ${-days}d ago`, tone: "danger" };
  if (days === 0) return { label: "Closes today", tone: "danger" };
  if (days <= 7) return { label: `${days}d left`, tone: "danger" };
  if (days <= 21) return { label: `${days}d left`, tone: "warning" };
  return { label: `${days}d left`, tone: "mute" };
}

const sourceTone = (s) => (s === "2Merkato" ? "teal" : "purple");
const isEthiopic = (bid) => /[ሀ-፿]/.test(bid.title || "");

// Bands come from tools/priority.mjs — fit alone is not actionable, so the
// board leads with what is winnable this week rather than what scores highest.
const BANDS = {
  act:     { label: "Act now",   tone: "accent",  blurb: "Strong fit, deadline still reachable, bond within reach" },
  prepare: { label: "Prepare",   tone: "info",    blurb: "Worth a decision soon" },
  late:    { label: "Too late?", tone: "danger",  blurb: "Closes within 2 days — documents and CPO likely cannot be arranged" },
  watch:   { label: "Watch",     tone: undefined, blurb: "Tracked, not urgent" },
};

function FocusCard({ bid, rank, onOpen }) {
  return (
    <button className="focus-card" onClick={() => onOpen(bid)}>
      <span className="focus-rank mono">{rank}</span>
      <span className="stack gap-6" style={{ flex: 1, minWidth: 0 }}>
        <span className={`focus-title${isEthiopic(bid) ? " ethiopic" : ""}`}>{bid.title}</span>
        {bid.buyer && <span className="dim bid-buyer">{bid.buyer}</span>}
        <span className="row gap-6" style={{ flexWrap: "wrap" }}>
          <Pill tone="accent" dot>{bid.reason}</Pill>
          <Pill tone={sourceTone(bid.source)}>{bid.source}</Pill>
          {bid.region !== "Unspecified" && <Pill tone="info">{bid.region}</Pill>}
        </span>
      </span>
      <span className="stack gap-2" style={{ alignItems: "center", minWidth: 52 }}>
        <MatchRing score={bid.priority} size={44} stroke={4} />
        <span className="mono dim" style={{ fontSize: 10, letterSpacing: ".06em" }}>PRIORITY</span>
      </span>
    </button>
  );
}

function LocalBidRow({ bid, onOpen }) {
  const days = bidDaysLeft(bid);
  const dl = bidDeadlineLabel(days);
  const band = BANDS[bid.band];

  return (
    <button className="bid-row" onClick={() => onOpen(bid)}>
      <MatchRing score={bid.match} size={38} stroke={3.5} />

      <span className="stack gap-6" style={{ flex: 1, minWidth: 0 }}>
        <span className={`bid-title${isEthiopic(bid) ? " ethiopic" : ""}`}>{bid.title}</span>
        {bid.buyer && <span className="dim bid-buyer">{bid.buyer}</span>}
        <span className="row gap-6" style={{ flexWrap: "wrap" }}>
          {band && band.label !== "Watch" && <Pill tone={band.tone} dot={bid.band === "act"}>{band.label}</Pill>}
          <Pill tone={sourceTone(bid.source)}>{bid.source}</Pill>
          <Pill tone="info">{bid.region}</Pill>
          <Pill tone={dl.tone === "mute" ? undefined : dl.tone} dot={dl.tone === "danger"}>{dl.label}</Pill>
          {bid.bidBond && <Pill tone="warning">Bond {bid.bidBond}</Pill>}
          {bid.matchedOn.slice(0, 2).map((m) => <Pill key={m} tone="accent">{m}</Pill>)}
        </span>
      </span>

      <span className="stack gap-2 bid-meta" style={{ textAlign: "right", minWidth: 82 }}>
        <span className="mono dim" style={{ fontSize: 11 }}>posted</span>
        <span className="mono" style={{ fontSize: 12 }}>{bid.posted || "—"}</span>
      </span>
    </button>
  );
}

function LocalBidDetail({ bid, onClose }) {
  if (!bid) return null;
  const days = bidDaysLeft(bid);
  const dl = bidDeadlineLabel(days);

  const facts = [
    ["Deadline", bid.deadline || "—"],
    ["Posted", bid.posted || "—"],
    ["Bid bond", bid.bidBond || "—"],
    ["Document fee", bid.docPrice || "—"],
    ["Tender type", bid.typeName || "—"],
    ["Published in", bid.publication || "—"],
  ];

  return (
    <React.Fragment>
      <div className="drawer-head">
        <div className="row between">
          <div className="row gap-6" style={{ flexWrap: "wrap" }}>
            <Pill tone={sourceTone(bid.source)} dot>{bid.source}</Pill>
            <Pill tone="info">{bid.region}</Pill>
            <Pill tone={dl.tone === "mute" ? undefined : dl.tone}>{dl.label}</Pill>
          </div>
          <button className="icon-btn" onClick={onClose} aria-label="Close">
            <Icon d={Icons.x} size={14} />
          </button>
        </div>
        <h2
          className={isEthiopic(bid) ? "ethiopic" : undefined}
          style={{ margin: 0, fontSize: 19, fontWeight: 600, letterSpacing: "-.01em", lineHeight: 1.45 }}
        >
          {bid.title}
        </h2>
        {bid.buyer && <div className="dim" style={{ fontSize: 12.5, marginTop: 6 }}>{bid.buyer}</div>}
      </div>

      <div className="drawer-body">
        <div className="grid grid-3" style={{ gap: 14, marginBottom: 24 }}>
          {facts.map(([label, value]) => (
            <div className="stack gap-4" key={label}>
              <div className="h-eyebrow">{label}</div>
              <div className="mono" style={{ fontSize: 12.5, wordBreak: "break-word" }}>{value}</div>
            </div>
          ))}
        </div>

        {BANDS[bid.band] && (
          <div className="verdict" style={{ marginBottom: 22 }}>
            <div className="row gap-8" style={{ marginBottom: 6 }}>
              <Pill tone={BANDS[bid.band].tone} dot={bid.band === "act"}>{BANDS[bid.band].label}</Pill>
              <span className="mono dim" style={{ fontSize: 11.5 }}>priority {bid.priority}/100</span>
            </div>
            <div style={{ fontSize: 13, lineHeight: 1.55 }}>{bid.reason || BANDS[bid.band].blurb}</div>
          </div>
        )}

        <SectionHead title="Why this scored where it did" kicker={`Fit score ${bid.match} / 100`} />
        {bid.matchedOn.length > 0 ? (
          <div className="row gap-6" style={{ flexWrap: "wrap", marginBottom: 10 }}>
            {bid.matchedOn.map((m) => <Pill key={m} tone="accent">{m}</Pill>)}
          </div>
        ) : (
          <div className="empty-note" style={{ marginBottom: 10 }}>
            No SuSu capability keywords found in the tender title.
          </div>
        )}
        {bid.excluded && bid.excluded.length > 0 && (
          <div className="stack gap-4" style={{ marginBottom: 10 }}>
            <div className="h-eyebrow">Counted against</div>
            <div className="row gap-6" style={{ flexWrap: "wrap" }}>
              {bid.excluded.map((m) => <Pill key={m} tone="danger">{m}</Pill>)}
            </div>
          </div>
        )}
        <div className="dim" style={{ fontSize: 12.5, lineHeight: 1.6, marginBottom: 22 }}>
          Scoring is keyword-based on the tender title only. Full scope, eligibility
          conditions and bidding documents live on the portal — treat this score as
          triage, not a decision.
        </div>

        {bid.alsoOn && bid.alsoOn.length > 0 && (
          <React.Fragment>
            <SectionHead title="Also listed on" kicker="Same tender, carried by more than one portal" />
            <div className="stack gap-8">
              {bid.alsoOn.map((alt) => (
                <a
                  key={alt.url}
                  className="popover-link"
                  href={alt.url}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  <span className="stack gap-2">
                    <span>{alt.source}</span>
                    <span className="dim">closing {alt.deadline || "—"}</span>
                  </span>
                  <Icon d={Icons.external} size={13} />
                </a>
              ))}
            </div>
          </React.Fragment>
        )}
      </div>

      <div className="drawer-foot">
        <a className="btn btn-primary" href={bid.sourceUrl} target="_blank" rel="noopener noreferrer">
          Open on {bid.source} <Icon d={Icons.external} size={14} />
        </a>
      </div>
    </React.Fragment>
  );
}

function LocalBidsView() {
  const all = window.LOCAL_BIDS || [];
  const meta = window.LOCAL_BIDS_META || {};
  // Opens on the focus set, not the full board — the point of the page is
  // "what should we bid on this week", not "here are 190 tenders".
  const [scope, setScope] = useLocalState("focus");
  const [region, setRegion] = useLocalState("all");
  const [source, setSource] = useLocalState("all");
  const [openBid, setOpenBid] = useLocalState(null);

  const regions = useLocalMemo(
    () => ["all", ...[...new Set(all.map((b) => b.region))].filter(Boolean).sort()],
    [all]
  );
  const sources = useLocalMemo(
    () => ["all", ...[...new Set(all.map((b) => b.source))].sort()],
    [all]
  );

  const rows = useLocalMemo(() => {
    let list = all;
    if (scope === "focus") list = list.filter((b) => b.relevant && b.band === "act");
    if (scope === "prepare") list = list.filter((b) => b.relevant && b.band === "prepare");
    if (scope === "relevant") list = list.filter((b) => b.relevant);
    if (region !== "all") list = list.filter((b) => b.region === region);
    if (source !== "all") list = list.filter((b) => b.source === source);
    return [...list].sort((a, b) => {
      const da = bidDaysLeft(a);
      const db = bidDaysLeft(b);
      const aOpen = (da ?? -1) >= 0;
      const bOpen = (db ?? -1) >= 0;
      if (aOpen !== bOpen) return aOpen ? -1 : 1;              // live tenders first
      if (b.priority !== a.priority) return b.priority - a.priority; // then actionability
      return (da ?? 9999) - (db ?? 9999);                      // then most urgent
    });
  }, [all, scope, region, source]);

  const relevant = all.filter((b) => b.relevant);
  const live = relevant.filter((b) => (bidDaysLeft(b) ?? -1) >= 0);
  const focus = relevant.filter((b) => b.band === "act").sort((a, b) => b.priority - a.priority);
  const prepare = relevant.filter((b) => b.band === "prepare");
  const tooLate = relevant.filter((b) => b.band === "late");
  const withBond = relevant.filter((b) => b.bidBond).length;
  const lastRun = meta.lastRun ? meta.lastRun.slice(0, 10) : "never";

  // On the focus scope the top 6 already appear as cards above, so the board
  // below picks up from there instead of repeating them.
  const boardRows = scope === "focus" ? rows.slice(6) : rows;

  return (
    <React.Fragment>
      <div className="hero fade-in" style={{ paddingBottom: 22 }}>
        <div className="hero-mesh" />
        <div className="row between" style={{ position: "relative", alignItems: "flex-start", gap: 24 }}>
          <div className="stack gap-12" style={{ maxWidth: 580 }}>
            <div className="row gap-8" style={{ flexWrap: "wrap" }}>
              {(meta.sources || []).map((s) => <Pill key={s} tone={sourceTone(s)} dot>{s}</Pill>)}
              <Pill tone="info">Synced {lastRun}</Pill>
            </div>
            <h1 className="h-title" style={{ fontSize: 30 }}>
              Local bids in Ethiopia.
              <span className="serif" style={{ display: "block", fontStyle: "italic", color: "var(--accent)", fontWeight: 400, fontSize: 30 }}>
                {focus.length} {focus.length === 1 ? "tender is" : "tenders are"} worth bidding on this week.
              </span>
            </h1>
            <div className="dim" style={{ fontSize: 14, lineHeight: 1.55, maxWidth: 520 }}>
              {live.length} open tenders match SuSu's <b style={{ color: "var(--text)" }}>IT infrastructure</b>,{" "}
              <b style={{ color: "var(--text)" }}>telecom</b> and <b style={{ color: "var(--text)" }}>AV systems</b>{" "}
              capabilities. Ranked by whether the deadline is still reachable and the
              bid bond is within reach — not by fit alone.
            </div>
          </div>

          <div className="stack gap-8" style={{ textAlign: "right", minWidth: 190, position: "relative" }}>
            <div className="h-eyebrow">Act this week</div>
            <div style={{ fontSize: 42, fontWeight: 600, letterSpacing: "-.03em", lineHeight: 1, fontVariantNumeric: "tabular-nums", color: "var(--accent)" }}>
              {focus.length}
            </div>
            <div className="dim" style={{ fontSize: 12.5 }}>
              of {live.length} open · from {meta.harvested || all.length} harvested
            </div>
          </div>
        </div>
      </div>

      <div className="grid grid-4">
        <Kpi label="Act now" value={focus.length} delta="winnable this week" deltaTone={focus.length ? "success" : "info"} />
        <Kpi label="Prepare" value={prepare.length} delta="decide soon" deltaTone="info" />
        <Kpi label="Closing in ≤2d" value={tooLate.length} delta="likely out of time" deltaTone={tooLate.length ? "warning" : "info"} />
        <Kpi label="Bid bond listed" value={withBond} delta={`of ${relevant.length} matched`} deltaTone="info" />
      </div>

      {scope === "focus" && focus.length > 0 && (
        <div style={{ marginTop: 26 }}>
          <SectionHead
            title="Focus this week"
            kicker={BANDS.act.blurb}
          />
          <div className="stack gap-8">
            {focus.slice(0, 6).map((bid, i) => (
              <FocusCard key={bid.id} bid={bid} rank={i + 1} onOpen={setOpenBid} />
            ))}
          </div>
        </div>
      )}

      <div style={{ marginTop: 26 }}>
        <SectionHead
          title={scope === "focus" ? "The rest of the focus set" : "Tender board"}
          kicker={`${rows.length} shown · ranked by priority, then urgency`}
          right={
            <div className="row gap-6">
              {[["focus", "Act now"], ["prepare", "Prepare"], ["relevant", "All matched"], ["all", "Everything"]].map(([id, label]) => (
                <button key={id} className={`btn btn-sm${scope === id ? " btn-primary" : ""}`} onClick={() => setScope(id)}>
                  {label}
                </button>
              ))}
            </div>
          }
        />

        <div className="row gap-6" style={{ flexWrap: "wrap", marginBottom: 14 }}>
          {sources.length > 2 && sources.map((s) => (
            <button key={s} className={`btn btn-sm${source === s ? " btn-primary" : " btn-ghost"}`} onClick={() => setSource(s)}>
              {s === "all" ? "All portals" : s}
            </button>
          ))}
          {regions.slice(0, 9).map((r) => (
            <button key={r} className={`btn btn-sm${region === r ? " btn-primary" : " btn-ghost"}`} onClick={() => setRegion(r)}>
              {r === "all" ? "All regions" : r}
            </button>
          ))}
        </div>

        <div className="stack gap-8">
          {boardRows.slice(0, 120).map((bid) => <LocalBidRow key={bid.id} bid={bid} onOpen={setOpenBid} />)}
        </div>

        {boardRows.length > 120 && (
          <div className="empty-note" style={{ marginTop: 12, textAlign: "center" }}>
            Showing the top 120 of {boardRows.length}. Narrow by portal or region to see the rest.
          </div>
        )}

        {boardRows.length === 0 && rows.length > 0 && scope === "focus" && (
          <div className="empty-note" style={{ textAlign: "center" }}>
            All {rows.length} shown above.
          </div>
        )}

        {rows.length === 0 && (
          <div className="card" style={{ padding: 28, textAlign: "center" }}>
            <div className="stack gap-10" style={{ alignItems: "center" }}>
              <Icon d={Icons.filter} size={22} />
              <div style={{ fontSize: 15, fontWeight: 500 }}>Nothing matches this filter</div>
              <div className="dim" style={{ fontSize: 13, lineHeight: 1.6, maxWidth: 460 }}>
                Try a different portal, region or scope.
              </div>
            </div>
          </div>
        )}

        {meta.filteredOut > 0 && (
          <div className="dim" style={{ fontSize: 12, marginTop: 18, textAlign: "center" }}>
            {meta.filteredOut} harvested tenders scored zero against SuSu's profile
            (construction, catering, medical, livestock) and are not shipped to this page.
          </div>
        )}
      </div>

      <Drawer open={!!openBid} onClose={() => setOpenBid(null)}>
        <LocalBidDetail bid={openBid} onClose={() => setOpenBid(null)} />
      </Drawer>
    </React.Fragment>
  );
}

Object.assign(window, { LocalBidsView, bidDaysLeft, bidDeadlineLabel });
