import { z } from 'zod'
import { SCOPES } from '../config.js'
import { defineTool, type ToolContext } from '../registry.js'

const store = z.string().max(64).optional().describe('Store view code (e.g. "default"). Omit for the default store view.')
const sku = z.string().min(1).max(128).describe('Exact product SKU')

const ALL_ISSUE_TYPES = [
  'missing_images', 'missing_description', 'missing_required_attributes', 'no_category', 'invalid_price',
  'enabled_not_visible', 'not_in_website', 'out_of_stock_enabled', 'configurable_without_children',
] as const
const STOCK_ISSUE_TYPES: readonly string[] = ['out_of_stock_enabled']

/** Live salable quantity from Magento (stock minus open-order reservations). */
async function liveSalable(ctx: ToolContext, productSku: string) {
  const stockId = ctx.env.MAGENTO_STOCK_ID
  const [qty, salable] = await Promise.all([ctx.magento.salableQty(productSku, stockId), ctx.magento.isSalable(productSku, stockId)])
  return { stockId, salableQty: qty, isSalable: salable, checkedAt: new Date().toISOString(), source: 'magento-live' }
}

export interface CatalogToolOptions {
  /** false when stock is not managed in Magento: no stock tool, filters or checks. */
  stock: boolean
}

export function catalogTools({ stock }: CatalogToolOptions) {
  const issueTypes = ALL_ISSUE_TYPES.filter(t => stock || !STOCK_ISSUE_TYPES.includes(t)) as [string, ...string[]]
  const tools = [
  defineTool({
    name: 'search_products',
    title: 'Search products',
    description:
      `Search the Magento catalog by keyword (matches SKU and product name, typo-tolerant ranking), optionally filtered by category, product type${stock ? ', status or stock' : ' or status'}. Returns SKU, name, status, visibility, price${stock ? ' and stock' : ''}. Use get_product for full detail.`,
    scope: SCOPES.CATALOG_READ,
    input: {
      query: z.string().max(200).optional().describe('Keyword, partial SKU or name'),
      category_id: z.number().int().positive().optional().describe('Only products in this category id (see list_categories)'),
      type: z.enum(['simple', 'configurable', 'bundle', 'grouped', 'virtual', 'downloadable']).optional(),
      status: z.enum(['enabled', 'disabled']).optional(),
      ...(stock ? { in_stock: z.boolean().optional() } : {}),
      limit: z.number().int().min(1).max(50).default(20),
      offset: z.number().int().min(0).default(0),
      store,
    },
    run: (a, { catalog }) =>
      catalog.get('/products', { q: a.query, category: a.category_id, type: a.type, status: a.status, in_stock: (a as { in_stock?: boolean }).in_stock, limit: a.limit, offset: a.offset, store: a.store }),
  }),

  defineTool({
    name: 'get_product',
    title: 'Get product details',
    description:
      `Full product detail for one SKU: names/descriptions for the store view, status, visibility, websites, price (base, special, effective, tier)${stock ? ', stock per source' : ''}, categories, images, variants/parents, related/upsell/cross-sell links, and all attributes with option labels.`,
    scope: SCOPES.CATALOG_READ,
    input: { sku, store },
    run: async (a, { catalog }) => {
      const product = await catalog.get<Record<string, unknown>>(`/products/${encodeURIComponent(a.sku)}`, { store: a.store })
      if (!stock) delete product.inventory
      return product
    },
  }),

  defineTool({
    name: 'diagnose_product_visibility',
    title: 'Why is this product not visible?',
    description:
      `Explains why a product may not show on the storefront for a store view: status, website assignment, visibility setting, categories, variants, ${stock ? 'stock (including live salable quantity), ' : ''}price and URL key. Returns each check with pass/fail and a summary.`,
    scope: SCOPES.CATALOG_READ,
    input: { sku, store },
    run: async (a, ctx) => {
      const diagnosis = await ctx.catalog.get<{ checks: Array<{ check: string; status: string; detail: string }>; likelyVisible: boolean; summary: string }>(
        `/products/${encodeURIComponent(a.sku)}/visibility`,
        { store: a.store },
      )
      if (!stock) return diagnosis
      const live = await liveSalable(ctx, a.sku).catch(() => null)
      if (live?.isSalable === false) {
        diagnosis.checks.push({ check: 'salable', status: 'warn', detail: `Magento reports the SKU is not salable right now (salable qty ${live.salableQty ?? 0}, after reservations).` })
      } else if (live?.isSalable) {
        diagnosis.checks.push({ check: 'salable', status: 'pass', detail: `Salable now: ${live.salableQty} units after reservations.` })
      }
      return { ...diagnosis, live }
    },
  }),

  defineTool({
    name: 'get_price',
    title: 'Get product price',
    description:
      'Current price for a SKU: base price, special price with its date window, tier prices, and the effective price. Set live=true to also fetch the exact shopper-facing price from the storefront (includes catalog price rules).',
    scope: SCOPES.CATALOG_READ,
    input: { sku, store, live: z.boolean().default(false).describe('Also ask the storefront for the final price incl. catalog price rules') },
    run: async (a, ctx) => {
      const stored = await ctx.catalog.get(`/prices/${encodeURIComponent(a.sku)}`, { store: a.store })
      if (!a.live) return { ...(stored as object), note: 'Excludes catalog price rules and customer-group pricing; use live=true for the storefront price.' }
      const storefront = await ctx.magento.storefrontPrice(a.sku, a.store ?? ctx.env.MAGENTO_DEFAULT_STORE)
      return { ...(stored as object), storefront, storefrontCheckedAt: new Date().toISOString() }
    },
  }),

  defineTool({
    name: 'get_stock',
    title: 'Get stock level',
    description:
      'Stock for a SKU: quantity per inventory source (synced every few minutes) plus a live salable-quantity check from Magento (stock minus quantities reserved by open orders). Answers "is this in stock?".',
    scope: SCOPES.CATALOG_READ,
    input: { sku, live: z.boolean().default(true).describe('Include a live salable-quantity check from Magento') },
    run: async (a, ctx) => {
      const synced = await ctx.catalog.get(`/inventory/${encodeURIComponent(a.sku)}`)
      return a.live ? { ...(synced as object), live: await liveSalable(ctx, a.sku) } : synced
    },
  }),

  defineTool({
    name: 'list_categories',
    title: 'List categories',
    description: 'Category tree for a store view with names, active flags, menu flags and product counts. Pass parent_id to list only the children of one category.',
    scope: SCOPES.CATALOG_READ,
    input: { parent_id: z.number().int().min(0).optional(), store },
    run: (a, { catalog }) => catalog.get('/categories', { parent: a.parent_id, store: a.store }),
  }),

  defineTool({
    name: 'get_category',
    title: 'Get category',
    description: 'One category with breadcrumb, active/menu flags, product count and direct children.',
    scope: SCOPES.CATALOG_READ,
    input: { category_id: z.number().int().positive(), store },
    run: (a, { catalog }) => catalog.get(`/categories/${a.category_id}`, { store: a.store }),
  }),

  defineTool({
    name: 'get_attribute',
    title: 'Get attribute',
    description: 'Definition of a product attribute by code (e.g. "color"): label, input type, whether required, scope and the list of allowed option values.',
    scope: SCOPES.CATALOG_READ,
    input: { attribute_code: z.string().min(1).max(128) },
    run: (a, { catalog }) => catalog.get(`/attributes/${encodeURIComponent(a.attribute_code)}`),
  }),

  defineTool({
    name: 'catalog_quality_summary',
    title: 'Catalog quality summary',
    description: `Counts of catalog data-quality issues for a store view: ${issueTypes.join(', ')}. Use find_products_with_issues to list the affected products.`,
    scope: SCOPES.CATALOG_READ,
    input: { store },
    run: (a, { catalog }) => catalog.get('/quality/summary', { store: a.store }),
  }),

  defineTool({
    name: 'find_products_with_issues',
    title: 'Find products with data issues',
    description:
      `Lists products with a given catalog data-quality issue, e.g. missing images, missing description, missing required attributes (shows which ones), no category, invalid price, enabled but not visible, not in any website${stock ? ', out of stock while enabled' : ''}, configurable without variants.`,
    scope: SCOPES.CATALOG_READ,
    input: {
      issue: z.enum(issueTypes),
      limit: z.number().int().min(1).max(50).default(25),
      offset: z.number().int().min(0).default(0),
      store,
    },
    run: (a, { catalog }) => catalog.get('/quality/issues', { type: a.issue, limit: a.limit, offset: a.offset, store: a.store }),
  }),

  defineTool({
    name: 'list_stores',
    title: 'List store views',
    description: 'Store views with their codes, websites, locale and currency. Use the code as the "store" argument of other tools.',
    scope: SCOPES.CATALOG_READ,
    input: {},
    run: (_a, { catalog }) => catalog.get('/stores'),
  }),

  defineTool({
    name: 'sync_status',
    title: 'Catalog data freshness',
    description: 'When the catalog copy was last synced from Magento (per data stream), the last run of each sync job, and the most recent failure. Use when asked how up to date the data is.',
    scope: SCOPES.CATALOG_READ,
    input: {},
    run: (_a, { catalog }) => catalog.get('/sync/status'),
  }),
  ]
  return stock ? tools : tools.filter(t => t.name !== 'get_stock')
}
