import { type JsonValue, fromCanonicalJson, CanonicalPrimitive } from './content-canonical'

export function buildContent(
  locale: string | undefined,
  locales: string[],
  variant: string | undefined,
  variants: string[],
  content: JsonValue
): [JsonValue, boolean] {
  function check(all: string[], specified: string[], query: string): boolean {
    const related = specified.filter((t) => all.includes(t))
    all = related.length ? related : all
    return all.includes(query)
  }

  function predicate(tags?: string[]) {
    if (!tags || !tags.length) {
      return true
    }

    if (locale && !check(locales, tags, locale)) {
      return false
    }

    return !variant || check(variants, tags, variant)
  }

  const options = { predicate: predicate, expand: false }

  const canonical = fromCanonicalJson(content)
  const reduced = canonical.reduce(options)

  return [reduced, options.expand]
}

export function expandContent(content: JsonValue, sources: JsonValue[]): JsonValue {
  function resolve(key: string): JsonValue {
    for (const source of sources) {
      const value = getPath(source, key)

      if (value !== undefined) {
        return value
      }
    }

    return `$\{${key}\}`
  }

  function traverse(item: JsonValue): JsonValue {
    if (item !== null) {
      if (item instanceof CanonicalPrimitive) {
        return item.resolve(resolve)
      } else if (Array.isArray(item)) {
        return item.map((i) => traverse(i))
      } else if (typeof item === 'object') {
        const record: Record<string, JsonValue> = {}

        for (const [k, v] of Object.entries(item)) {
          record[k] = traverse(v)
        }

        return record
      }
    }

    return item
  }

  return traverse(content)
}

// function getPath(obj: any, path: string, sep = '.'): any { // TODO REFACTOR THIS
//   const keys = path.split(sep)
//   let cur = obj
//
//   for (const key of keys) {
//     if (cur == null || typeof cur !== 'object') return undefined
//     cur = cur[key]
//   }
//
//   return cur
// }

function toPath(path: string): Array<string | number> {
  // Matches:
  //  - bare tokens: foo, bar
  //  - [123] numeric indices
  //  - ["str"] or ['str'] quoted keys
  const re = /[^.[\]]+|\[(?:([0-9]+)|(["'])(.*?)\2)\]/g
  const tokens: Array<string | number> = []
  let m: RegExpExecArray | null

  while ((m = re.exec(path))) {
    if (m[1] !== undefined) {
      tokens.push(Number(m[1])) // [123]
    } else if (m[3] !== undefined) {
      tokens.push(m[3]) // ["key"] / ['key']
    } else {
      tokens.push(m[0]) // bare token
    }
  }
  return tokens
}

function getPath(obj: unknown, path: string): JsonValue | undefined {
  const steps = toPath(path)
  let cur: any = obj

  for (const step of steps) {
    if (cur == null) return undefined

    if (typeof step === 'number') {
      if (!Array.isArray(cur) || step < 0 || step >= cur.length) return undefined
      cur = cur[step]
    } else {
      cur = cur[step]
    }
  }
  return cur as JsonValue
}
