//! The developer's path through WAI and the agent door, one operation a module:
//!
//! - `toolchain.test` (`wai hello`): the four steps of WAI (name, dispatch, rebuild,
//!   receipt) on a bundled sample;
//! - `project.create` (`wai create`): a new project from one of the examples
//!   (`wai/examples/`), or from an app made with Create (`--from app.oui`, `app.export`);
//! - `project.lock`, `project.check`, `project.build` (`wai lock`, `wai check`, `wai build`): a
//!   project pinned in `wai.lock`, validated without building it, and written to `dist/`, the same
//!   bytes from the same project and lock (`lock.rs`, `check.rs`, `build.rs`);
//! - `dev.start` (`wai dev`): a relay, a room service and the room page on one origin, on this
//!   machine;
//! - `receipt.verify` (`wai verify`): the receipts under a directory, conformance results,
//!   fleet report bundles and envelopes' pins, checked offline; with `--conformant`, each
//!   conformance result admits its implementation only for a run that passed against a
//!   checkout's corpus;
//! - `compat.check` (`wai breaking`): what the working tree breaks against a git ref;
//! - `conformance.run` (`wai conform`): WAI's conformance corpus run against another
//!   implementation, its result a signed record; `conformance.list` (`wai conformance list`,
//!   also `wai conform --list`) the kinds and suites;
//! - `app.guide`, `app.check`, `app.export` (`wai app guide|check|export`; a build with
//!   `app`): an app made with Create;
//! - `mcp.start` (`wai mcp`): WAI's operations as tools for a coding agent, a Model Context
//!   Protocol server on standard input and output, in toolsets (`../mcp/`);
//! - `agent.setup` (`wai setup-agent`): a project made ready for coding agents
//!   (`../setup_agent.rs`);
//! - `enrolment.create`, `enrolment.list` (`wai enrolment create|list`) and `report.create`,
//!   `report.export` (`wai report create|export`): a fleet's nodes enrolled and its reports,
//!   against a fleet service (extensions/fleet.md §9; `fleet.rs`);
//! - `channel.import`, `channel.guide` and `channel.verify` (`wai channel import|guide|verify`):
//!   a linear channel's feeds imported into its signed receipt and lineup, its guide, and the
//!   three checked together (extensions/channel-lineup.md §9; `channel.rs`).
//! - `contract.create`, `contract.update` (`wai contract create|accept`) and `statement.create`,
//!   `statement.verify` (`wai statement create|verify`): a channel's share contract and the
//!   revenue-share statements recomputed from the ledger's invoices and the channel's
//!   program-as-run (extensions/revenue-share.md §10; `share.rs`);
//! - `opportunity.create`, `opportunity.verify` (`wai opportunity create|verify`) and
//!   `statement.check` (`wai statement check`): the ad opportunities a channel's signed records
//!   prove for an owner, and a platform's reported figures checked against them
//!   (extensions/opportunity-report.md §12; `opportunity.rs`).
//! - `key.create`, `key.list`, `key.show`, `key.rotate`, `key.revoke`, `key.import` and
//!   `key.export` (`wai key create|list|show|rotate|revoke|import|export`): a developer's signing
//!   keys in one keystore, each named, so that every signing operation's `--key` takes its name
//!   (`key.rs`).
//! - `docs.search` (`wai docs search`) and `registry.show` (`wai registry show`): the specification
//!   searched and one capability shown, by the tools `wai mcp` serves them with (`lookup.rs`).
//! - `toolchain.doctor` (`wai doctor`): whether this machine can run the toolchain, one check to a
//!   line, each with its result and what to do when it fails (`doctor.rs`).
//!
//! [`envelope`] holds the agent rules every command keeps: the exit codes, the JSON envelope,
//! `--dry-run`, `--yes`.
//!
//! [`VERBS`] is this module's view of the operation catalogue (`wai::catalogue`): each
//! entry names its operation by id, and the name a person types, its usage and its help come
//! from the catalogue's entry. The project file a template writes (`wai.json`) is read by
//! [`project`] alone.

pub mod project;

mod app;
mod breaking;
pub(crate) mod build;
mod channel;
pub(crate) mod check;
#[cfg(feature = "quickstart")]
pub(crate) mod conform;
mod create;
mod dev;
pub(crate) mod doctor;
pub(crate) mod envelope;
mod fleet;
mod hello;
pub(crate) mod lock;
mod opportunity;
mod share;
mod verify;
// A developer's signing keys (`wai key`), and the keys every signing operation's `--key` names.
pub(crate) mod key;
mod lookup;

#[cfg(feature = "mcp")]
#[path = "../mcp/mod.rs"]
mod mcp;
#[path = "../setup_agent.rs"]
mod setup_agent;

use crate::door::{Impl, Json};
use envelope::OpError;

/// The verb `wai mcp` is called by; `setup-agent` registers the server under it.
pub const MCP: &str = "mcp";

/// The developer's path, by operation id.
pub const VERBS: &[Impl] = &[
    Impl { id: "toolchain.test", run: hello::main, json: Json::Text },
    Impl { id: "project.create", run: create::main, json: Json::OwnWith("--list") },
    Impl { id: "project.lock", run: lock::main, json: Json::Native },
    Impl { id: "project.check", run: check::main, json: Json::Native },
    Impl { id: "project.build", run: build::main, json: Json::Native },
    Impl { id: "dev.start", run: dev::main, json: Json::StreamUnless("--check") },
    Impl { id: "receipt.verify", run: verify::main, json: Json::Text },
    Impl { id: "compat.check", run: compat_check, json: Json::Native },
    Impl { id: "conformance.run", run: conformance_run, json: Json::Native },
    Impl { id: "conformance.list", run: conformance_list, json: Json::Native },
    Impl { id: "app.guide", run: app_guide, json: Json::Text },
    Impl { id: "app.check", run: app_check, json: Json::Native },
    Impl { id: "app.export", run: app_export, json: Json::Text },
    Impl { id: "mcp.start", run: mcp_main, json: Json::Native },
    Impl { id: "agent.setup", run: setup_agent::main, json: Json::Native },
    Impl { id: "enrolment.create", run: fleet::enrolment_create, json: Json::Native },
    Impl { id: "enrolment.list", run: fleet::enrolment_list, json: Json::Native },
    Impl { id: "report.create", run: fleet::report_create, json: Json::Native },
    Impl { id: "report.export", run: fleet::report_export, json: Json::Native },
    Impl { id: "channel.import", run: channel::import, json: Json::Native },
    Impl { id: "channel.guide", run: channel::guide, json: Json::Native },
    Impl { id: "channel.verify", run: channel::verify, json: Json::Native },
    // The revenue share (extensions/revenue-share.md §10).
    Impl { id: "contract.create", run: share::contract_create, json: Json::Native },
    Impl { id: "contract.update", run: share::contract_update, json: Json::Native },
    Impl { id: "statement.create", run: share::statement_create, json: Json::Native },
    Impl { id: "statement.verify", run: share::statement_verify, json: Json::Native },
    // The opportunity report and the statement check (extensions/opportunity-report.md §12).
    Impl { id: "opportunity.create", run: opportunity::opportunity_create, json: Json::Native },
    Impl { id: "opportunity.verify", run: opportunity::opportunity_verify, json: Json::Native },
    Impl { id: "statement.check", run: opportunity::statement_check, json: Json::Native },
    // A developer's signing keys, in one keystore (`key.rs`).
    Impl { id: "key.create", run: key::create, json: Json::Native },
    Impl { id: "key.list", run: key::list, json: Json::Native },
    Impl { id: "key.show", run: key::show, json: Json::Native },
    Impl { id: "key.rotate", run: key::rotate, json: Json::Native },
    Impl { id: "key.revoke", run: key::revoke, json: Json::Native },
    Impl { id: "key.import", run: key::import, json: Json::Native },
    Impl { id: "key.export", run: key::export, json: Json::Native },
    // Two operations whose tool `wai mcp` also serves (`lookup.rs`).
    Impl { id: "docs.search", run: lookup::docs_search, json: Json::Native },
    Impl { id: "registry.show", run: lookup::registry_show, json: Json::Native },
    // Whether this machine can run the toolchain (`doctor.rs`).
    Impl { id: "toolchain.doctor", run: doctor::main, json: Json::Native },
];

/// An operation's synopsis, from the catalogue, for a usage error.
pub(crate) fn usage(id: &str) -> String {
    let o = wai::catalogue::op(id).expect("an operation of the catalogue");
    o.usage.lines().next().map(|l| format!("wai {l}")).unwrap_or_default()
}

/// `args` with the agent flags `wai` read off the command line handed back, those of `takes`
/// that the operation reads itself (`--json`, `--dry-run`). `--yes` is read where a change
/// waits for it, from [`envelope::ctx`] ([`envelope::confirm`]).
fn with_agent_flags(args: &[String], takes: &[&str]) -> Vec<String> {
    let c = envelope::ctx();
    let mut a = args.to_vec();
    for (on, flag) in [(c.json, "--json"), (c.dry_run, "--dry-run")] {
        if on && takes.contains(&flag) && !a.iter().any(|x| x == flag) {
            a.push(flag.into());
        }
    }
    a
}

fn compat_check(args: &[String]) -> Result<bool, OpError> {
    Ok(breaking::main(&with_agent_flags(args, &["--json"]))?)
}

#[cfg(feature = "quickstart")]
fn conformance_run(args: &[String]) -> Result<bool, OpError> {
    Ok(conform::main(&with_agent_flags(args, &["--json", "--dry-run"]))?)
}

#[cfg(feature = "quickstart")]
fn conformance_list(args: &[String]) -> Result<bool, OpError> {
    let mut a = vec!["--list".to_string()];
    a.extend(with_agent_flags(args, &["--json"]));
    Ok(conform::main(&a)?)
}

#[cfg(not(feature = "quickstart"))]
fn conformance_run(_: &[String]) -> Result<bool, OpError> {
    Err(crate::door::not_built(wai::catalogue::op("conformance.run").expect("listed")))
}

#[cfg(not(feature = "quickstart"))]
fn conformance_list(_: &[String]) -> Result<bool, OpError> {
    Err(crate::door::not_built(wai::catalogue::op("conformance.list").expect("listed")))
}

/// `words` then `args`.
fn with(words: &[&str], args: &[String]) -> Vec<String> {
    words.iter().map(|w| w.to_string()).chain(args.iter().cloned()).collect()
}

fn app_guide(args: &[String]) -> Result<bool, OpError> {
    Ok(app::main(&with(&["guide"], args))?)
}

fn app_check(args: &[String]) -> Result<bool, OpError> {
    Ok(app::main(&with(&["check"], &with_agent_flags(args, &["--json"])))?)
}

/// `wai app export <app.oui> <dir> [--runtime DIR]`: `wai create <dir> --from <app.oui>`.
fn app_export(args: &[String]) -> Result<bool, OpError> {
    let pos = positional(args, &["--runtime"]);
    let [program, dir] = pos.as_slice() else {
        return Err(OpError::usage(usage("app.export"), ""));
    };
    let mut a = vec![dir.to_string(), "--from".into(), program.to_string()];
    if let Some(r) = opt(args, "--runtime") {
        a.extend(["--runtime".into(), r.to_string()]);
    }
    create::main(&a)
}

#[cfg(feature = "mcp")]
fn mcp_main(args: &[String]) -> Result<bool, OpError> {
    if crate::out::dry_run() {
        return mcp::plan(args).map_err(OpError::from);
    }
    mcp::main(args).map_err(OpError::from)
}

#[cfg(not(feature = "mcp"))]
fn mcp_main(_: &[String]) -> Result<bool, OpError> {
    Err(crate::door::not_built(wai::catalogue::op("mcp.start").expect("listed")))
}

/// The value after `flag` among `args`.
pub(crate) fn opt<'a>(args: &'a [String], flag: &str) -> Option<&'a str> {
    args.iter().position(|a| a == flag).and_then(|i| args.get(i + 1)).map(String::as_str)
}

/// Whether `flag` is among `args`.
pub(crate) fn flag(args: &[String], f: &str) -> bool {
    args.iter().any(|a| a == f)
}

/// The arguments that are neither flags nor a flag's value, given the flags that take one.
pub(crate) fn positional<'a>(args: &'a [String], with_value: &[&str]) -> Vec<&'a str> {
    let mut out = Vec::new();
    let mut i = 0;
    while i < args.len() {
        let a = args[i].as_str();
        if with_value.contains(&a) {
            i += 2;
            continue;
        }
        if !a.starts_with("--") {
            out.push(a);
        }
        i += 1;
    }
    out
}

/// Lowercase hexadecimal.
#[cfg(feature = "quickstart")]
pub(crate) fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}
