use serde_json::{Value, json};

pub fn describe() -> Value {
    json!({
        "clispec": "1",
        "name": "onym",
        "version": env!("CARGO_PKG_VERSION"),
        "description": env!("CARGO_PKG_DESCRIPTION"),
        "homepage": env!("CARGO_PKG_REPOSITORY"),
        "commands": [check_command(), schema_command()],
        "errors": errors_block(),
        "conventions": conventions_block()
    })
}

fn check_command() -> Value {
    json!({
        "name": "check",
        "summary": "Check name availability across registries and domains",
        "mutates": false,
        "idempotent": true,
        "arguments": [{
            "name": "names",
            "description": "One or more names to check",
            "variadic": true,
            "required": true,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$"
        }],
        "flags": check_flags(),
        "exit_codes": {
            "0": "All names free across all sources",
            "1": "At least one name is taken (no active projects)",
            "2": "At least one name has an ACTIVE project holding it",
            "3": "Invalid input",
            "4": "Network, timeout, rate limit, or upstream failure",
            "5": "Parse or internal error"
        },
        "output_schema": check_output_schema()
    })
}

fn check_flags() -> Value {
    json!([
        {
            "name": "output",
            "short": "o",
            "type": "enum",
            "values": ["auto", "json", "table", "markdown"],
            "default": "auto",
            "description": "Output format. 'auto' emits table when stdout is a TTY, else JSON."
        },
        {
            "name": "json",
            "type": "bool",
            "description": "Alias for --output json."
        },
        {
            "name": "tld",
            "type": "string",
            "repeatable": true,
            "description": "Override default domain TLDs. Repeat to add multiple."
        }
    ])
}

fn check_output_schema() -> Value {
    json!({
        "type": "array",
        "items": {
            "type": "object",
            "required": ["name", "results"],
            "properties": {
                "name": {"type": "string"},
                "results": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "required": ["source", "status"],
                        "properties": {
                            "source": {
                                "type": "string",
                                "description": "Registry id (crates.io, pypi, npm, github) or '.<tld>'"
                            },
                            "status": status_schema()
                        }
                    }
                }
            }
        }
    })
}

fn status_schema() -> Value {
    json!({
        "oneOf": [
            {"type": "object", "properties": {"state": {"const": "free"}}},
            {
                "type": "object",
                "properties": {
                    "state": {"const": "taken"},
                    "version": {"type": ["string", "null"]},
                    "description": {"type": ["string", "null"]},
                    "owner": {"type": ["string", "null"]},
                    "downloads": {"type": ["integer", "null"]},
                    "recent_downloads": {"type": ["integer", "null"]},
                    "last_release": {"type": ["string", "null"]},
                    "classification": {
                        "enum": ["active", "placeholder", "dead", "unknown"]
                    }
                }
            },
            {
                "type": "object",
                "properties": {
                    "state": {"const": "unknown"},
                    "reason": {"type": "string"}
                }
            }
        ]
    })
}

fn schema_command() -> Value {
    json!({
        "name": "schema",
        "summary": "Print this machine-readable tool description",
        "mutates": false,
        "idempotent": true,
        "arguments": [],
        "flags": [],
        "exit_codes": {"0": "Success"}
    })
}

fn errors_block() -> Value {
    json!({
        "kinds": [
            {"kind": "invalid_input", "retryable": false, "exit_code": 3},
            {"kind": "network",       "retryable": true,  "exit_code": 4},
            {"kind": "timeout",       "retryable": true,  "exit_code": 4},
            {"kind": "rate_limit",    "retryable": true,  "exit_code": 4},
            {"kind": "upstream",      "retryable": true,  "exit_code": 4},
            {"kind": "parse",         "retryable": false, "exit_code": 5},
            {"kind": "internal",      "retryable": false, "exit_code": 5}
        ],
        "envelope": {
            "destination": "stderr",
            "format": "json",
            "shape": {"error": {"kind": "string", "message": "string", "retryable": "bool"}}
        }
    })
}

fn conventions_block() -> Value {
    json!({
        "stdout": "structured data only",
        "stderr": "diagnostics and errors",
        "tty_detection": true,
        "non_interactive": true,
        "idempotent": true
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn schema_contains_core_fields() {
        let s = describe();
        assert_eq!(s["name"], "onym");
        assert!(s["commands"].is_array());
        assert!(s["errors"]["kinds"].is_array());
    }

    #[test]
    fn check_command_is_read_only() {
        let s = describe();
        let check = s["commands"]
            .as_array()
            .unwrap()
            .iter()
            .find(|c| c["name"] == "check")
            .unwrap();
        assert_eq!(check["mutates"], false);
        assert_eq!(check["idempotent"], true);
    }

    #[test]
    fn schema_exposes_every_error_kind() {
        let s = describe();
        let kinds: Vec<_> = s["errors"]["kinds"]
            .as_array()
            .unwrap()
            .iter()
            .map(|k| k["kind"].as_str().unwrap().to_string())
            .collect();
        for expected in [
            "invalid_input",
            "network",
            "timeout",
            "rate_limit",
            "upstream",
            "parse",
            "internal",
        ] {
            assert!(
                kinds.iter().any(|k| k == expected),
                "missing kind {expected}"
            );
        }
    }
}
