import { type CommandShape, Command } from "./command"
import { ParseError } from "./errors"
import { expect, test } from "bun:test"

const fetchShape: CommandShape = {
  command: "fetch",
  description: "Fetch a URL and display the response",
  args: [
    { name: "url", type: "string" },
    { name: "raw", type: "boolean", named: true, default: false },
    { name: "timeout", type: "number", named: true, default: 30 },
  ],
}

test("parseArgs with valid args", () => {
  const cmd = new Command(fetchShape)
  const { args, error } = cmd.parse("fetch https://example.com raw=true timeout=10")
  expect(error).toBeUndefined()
  expect(args).toMatchObject([
    { name: "url", value: "https://example.com" },
    { name: "raw", value: true },
    { name: "timeout", value: 10 },
  ])
})

test("parseArgs without some named args", () => {
  const cmd = new Command(fetchShape)
  const { args, error } = cmd.parse("fetch https://example.com raw=true")
  expect(error).toBeUndefined()
  expect(args).toEqual([
    { name: "url", value: "https://example.com" },
    { name: "raw", value: true },
  ])
})

test("parseArgs with too many named args", () => {
  const cmd = new Command(fetchShape)
  const { error } = cmd.parse("fetch https://example.com meow")
  expect(error).toBeDefined()
  expect(error?.message).toEqual("Expected 1 positional argument(s) but got 2.")
  expect(error?.start).toEqual(30)
  expect(error?.end).toEqual(30)
})

test("parseArgs with not enough positional args", () => {
  const cmd = new Command(fetchShape)
  const { error } = cmd.parse("fetch")
  expect(error).toBeDefined()
  expect(error?.message).toEqual("Expected 1 positional argument(s) but got 0.")
  expect(error?.start).toEqual(5)
  expect(error?.end).toEqual(5)
})

const typeShape: CommandShape = {
  command: "type",
  description: "Fetch a URL and display the response",
  args: [
    { name: "theBoolean", type: "boolean", named: true, default: false },
    { name: "theNumber", type: "number", named: true, default: 30 },
  ],
}

test("parseArgs incorrect number type", () => {
  const cmd = new Command(typeShape)
  const { error } = cmd.parse("type theNumber=notanumber")
  expect(error).toBeDefined()

  expect(error?.message).toEqual("Expected a number but got 'notanumber'.")
  expect(error?.start).toEqual(5)
  expect(error?.end).toEqual(25)
})

test("parseArgs incorrect boolean type", () => {
  const cmd = new Command(typeShape)
  const { error } = cmd.parse("type theBoolean=notaboolean")
  expect(error).toBeDefined()

  expect(error?.message).toEqual("Expected a boolean but got 'notaboolean'.")
  expect(error?.start).toEqual(5)
  expect(error?.end).toEqual(27)
})

test("argSuggestions for positional and named args", () => {
  const cmd = new Command(fetchShape)

  const expectNames = (input: string) => {
    const names = cmd.getSuggestions(input).suggestions.map((a) => a.name + (a.named ? "=" : ""))
    return expect(names, `from input "${input}"`)
  }

  expectNames("fetch ").toEqual(["url"])
  expectNames("fetch path r").toEqual(["raw="])
  expectNames("fetch path raw").toEqual(["raw="])
  expectNames("fetch path raw=true ").toEqual(["timeout="])
  expectNames("fetch path raw=true t").toEqual(["timeout="])
})

test("argSuggestions throw errors for unmatched patterns", () => {
  const cmd = new Command(fetchShape)

  const expectError = (input: string) => {
    const error = cmd.getSuggestions(input).error
    return expect(error, `from input "${input}"`)
  }

  expectError("fetch path f").toBeInstanceOf(ParseError)
  expectError("fetch path raw=asdf ").toBeInstanceOf(ParseError)
  expectError("fetch path raw=true").toBeUndefined()
})

/*
/*
# Ignored problems

what if a positional arg looks like a named arg? For example echo verbose=32. If we want to echo verbose=32 it is ambiguous.
*/
