// ---------------------------------------------------------------------------
// settings / ModulePermsList.tsx — THE per-database permission list. (wave 33,
// owner items 10 + 11 · W33-T34 / W33-T38.)
//
// ⭐ WHY THIS FILE EXISTS, IN THE OWNER'S OWN WORDS: *"under manage user (and
// manage agent too since its the same exact layout)"*. "The same exact layout"
// has exactly one honest implementation, and it is not two components kept in
// step by intention. `PermsEditor` and `ManageAgentPane` both render THIS, so a
// change to the disclosure or the summary line reaches both or neither — and
// wave 36 is the proof: deleting the "Not set here" card (W36-T22 / C2, owner
// item 11) took it out of the agent pane too, in the same edit, with nothing to
// remember.
//
// ⛔ THE ALTERNATIVE, NAMED SO NOBODY RE-DERIVES IT. Copying the JSX into the
// agent pane would have been faster today and is the exact shape of every defect
// this repo keeps re-finding: `may_open` vs the automation table picker (wave
// 20), `clean_fields` vs `_clean_field` (wave 28), `FILTER_OPS`' three mirrors.
// Two renderings of one rule disagree in front of a user, and each looks correct
// read on its own.
//
// PURELY PRESENTATIONAL. It owns exactly one piece of state — WHICH row is
// expanded — because that is a property of this list and of nothing else. Every
// value it shows and every write it makes belongs to the caller: the account
// editor PUTs to `/admin/users/{u}/perms`, the agent editor to its own door, and
// this component knows about neither.
// ---------------------------------------------------------------------------

import { useState } from "react";
import type { ReactNode } from "react";
import { FilterBuilderPanel, FieldsHidePanel } from "../filter-kit";
import type { FilterTree } from "../customer-grid/types";
import type { PermsModule, PermsRecord } from "./permsModel";
import { filterOf, hiddenSet, hideableKeys, identityKey, moduleSummary } from "./permsModel";
import "./perms.css";

export interface ModulePermsListProps {
  /** In server order — the order the sections render in. */
  modules: PermsModule[];
  /** The DRAFT being edited, keyed by module. */
  entries: PermsRecord;
  onAccess: (key: string, on: boolean) => void;
  onFilter: (key: string, next: FilterTree | null) => void;
  onToggleHidden: (key: string, fieldKey: string) => void;
  onSetHidden: (key: string, keys: string[]) => void;
  /** People this tenant can name in a `user` condition. Absent ⇒ the panel says so. */
  userOptions?: string[];
  /** Anything the CALLER wants in a row's head — the account editor's "Copy to…"
   *  door. Returning null is the normal case and costs the row nothing. */
  headExtra?: (m: PermsModule) => ReactNode;
  /** Shown when the tenant governs nothing at all. */
  emptyNote?: string;
}

export function ModulePermsList({
  modules,
  entries,
  onAccess,
  onFilter,
  onToggleHidden,
  onSetHidden,
  userOptions,
  headExtra,
  emptyNote,
}: ModulePermsListProps) {
  // ⭐ Owner item 11: *"the database should not show all immediately the detail
  // (for filter or hide fields)"*. Single-valued rather than a Set, deliberately
  // — the complaint was a wall of panels, and an accordion cannot become one by
  // accumulation.
  const [openDetail, setOpenDetail] = useState<string | null>(null);

  if (modules.length === 0) {
    return (
      <p className="pg-empty">
        {emptyNote ?? "This workspace has no databases whose access can be restricted."}
      </p>
    );
  }

  return (
    <>
      {modules.map((m) => {
        const entry = entries[m.key];
        const on = entry?.access === true;
        const schemaless = m.fields.length === 0;
        const shown = openDetail === m.key;
        // ⭐⭐ WAVE 36 (W36-T22 / CONTRACT C2 / OWNER RULING R6) — THE `enforced`
        // BRANCH AND ITS "Not set here" CARD ARE DELETED. Owner item 11, verbatim:
        // *"how come the database is only toggleable for Odoo customers and Odoo
        // products. EVERY database should be able to be toggleable by admin. I'm
        // only seeing 'Not set here'."*
        //
        // That card was HONEST when it shipped — `perm_scope` was never consulted
        // on a `ut_*` read, so an access toggle there would have been a control
        // that lies. W36-T21 armed the wall over every database, so the branch's
        // premise is now false and rendering it would be the lie it was written to
        // prevent, facing the other way. Every row below is a row whose rule the
        // table routes apply.
        const canDetail = on && !schemaless;

        return (
          <section className="set-card set-perm-mod" key={m.key}>
            <div className="set-perm-modhead">
              <label className="set-check set-perm-toggle">
                <input
                  type="checkbox"
                  checked={on}
                  onChange={(e) => onAccess(m.key, e.target.checked)}
                />
                <span className="set-perm-modname">{m.label}</span>
              </label>
              {/* ⚠ Wrapped rather than left as siblings of the toggle:
                  `.set-perm-modhead` is `space-between` and lives in `index.css`,
                  which is lane B's under contract C4. Grouping the trailing
                  controls keeps every new one off that rule — one flex child in,
                  one flex child out. */}
              <span className="set-perm-headend">
                <span className="set-perm-sum">{moduleSummary(entry, schemaless)}</span>
                {canDetail ? (
                  <button
                    type="button"
                    className="set-secondary set-perm-disclose"
                    aria-expanded={shown}
                    onClick={() => setOpenDetail((k) => (k === m.key ? null : m.key))}
                  >
                    {shown ? "Hide detail" : "Conditions and fields"}
                  </button>
                ) : null}
                {headExtra?.(m) ?? null}
              </span>
            </div>

            {/* R9's fail-closed rendering: no readable schema means the access
                toggle and nothing else. The record it saves says the same thing —
                no filter, no hidden fields — so the editor and the payload cannot
                disagree (`permsModel.toPutBody`). */}
            {on && schemaless ? (
              <p className="set-help">
                {m.surface
                  ? "Access is the whole rule for this module. It has no fields to hide " +
                    "and no records to filter."
                  : "No field list is available for this database, so access is all this " +
                    "editor can set for it. Conditions and hidden fields need a schema."}
              </p>
            ) : null}

            {canDetail && shown ? (
              <div className="set-perm-panels">
                <div className="cg-pop set-perm-pop">
                  <FilterBuilderPanel
                    fields={m.fields}
                    filters={filterOf(entries, m.key)}
                    onChange={(next) => onFilter(m.key, next)}
                    userOptions={userOptions}
                  />
                </div>
                <div className="cg-pop set-perm-pop">
                  <FieldsHidePanel
                    fields={m.fields}
                    hidden={hiddenSet(entries, m.key)}
                    onToggle={(key) => onToggleHidden(m.key, key)}
                    // ⚠ `lockedKey` IS NOT OPTIONAL HERE, whatever the prop says.
                    // Absent, the panel locks nothing and one click on "Hide all"
                    // hides the row's own name too — a record whose faithful
                    // enforcement is a table of blank rows, which the server's PUT
                    // validation would accept because the identity column is a
                    // perfectly KNOWN field key.
                    lockedKey={identityKey(m.fields)}
                    onHideAll={() => onSetHidden(m.key, hideableKeys(m.fields))}
                    onShowAll={() => onSetHidden(m.key, [])}
                  />
                </div>
              </div>
            ) : null}
          </section>
        );
      })}
    </>
  );
}
