import { and, eq, inArray, ne } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { getDefaultLocale } from './languages.js'
import type { GetTranslationsArgs, GetTranslationsForArgs } from './model.js'
import { translationValue, type I18nContentSchema } from './schema.js'
import { assertEntityType, assertFieldKey } from './validate.js'

type TranslationRow = {
  fieldKey: string
  locale: string
  value: string
}

function validateReadArgs(entityType: string, fields?: readonly string[]): void {
  assertEntityType(entityType)
  if (fields) {
    for (const field of fields) {
      assertFieldKey(field)
    }
  }
}

function buildReadConditions(
  entityType: string,
  locales: readonly string[],
  fields?: readonly string[],
) {
  const conditions = [
    eq(translationValue.entityType, entityType),
    inArray(translationValue.locale, [...locales]),
    ne(translationValue.status, 'FAILED'),
    ne(translationValue.value, ''),
  ]

  if (fields && fields.length > 0) {
    conditions.push(inArray(translationValue.fieldKey, [...fields]))
  }

  return conditions
}

function mergeLocaleRows(
  rows: readonly TranslationRow[],
  requestedLocale: string,
  fallbackToDefault: boolean,
): Record<string, string> {
  const primary = new Map<string, string>()
  const fallback = new Map<string, string>()

  for (const row of rows) {
    if (row.locale === requestedLocale) {
      primary.set(row.fieldKey, row.value)
    } else if (fallbackToDefault) {
      fallback.set(row.fieldKey, row.value)
    }
  }

  const result: Record<string, string> = {}
  const keys = new Set([...primary.keys(), ...(fallbackToDefault ? fallback.keys() : [])])

  for (const key of keys) {
    const value = primary.get(key) ?? fallback.get(key)
    if (value !== undefined) {
      result[key] = value
    }
  }

  return result
}

async function resolveLocales(
  q: Querier<I18nContentSchema>,
  locale: string,
  fallbackToDefault: boolean,
): Promise<string[]> {
  if (!fallbackToDefault) {
    return [locale]
  }

  const defaultLocale = await getDefaultLocale(q)
  if (defaultLocale === locale) {
    return [locale]
  }

  return [locale, defaultLocale]
}

export async function getTranslations(
  q: Querier<I18nContentSchema>,
  args: GetTranslationsArgs,
): Promise<Record<string, string>> {
  validateReadArgs(args.entityType, args.fields)

  const fallbackToDefault = args.fallbackToDefault ?? false
  const locales = await resolveLocales(q, args.locale, fallbackToDefault)

  const rows = await q
    .select({
      fieldKey: translationValue.fieldKey,
      locale: translationValue.locale,
      value: translationValue.value,
    })
    .from(translationValue)
    .where(
      and(
        eq(translationValue.entityId, args.entityId),
        ...buildReadConditions(args.entityType, locales, args.fields),
      ),
    )

  return mergeLocaleRows(rows, args.locale, fallbackToDefault)
}

export async function getTranslationsFor(
  q: Querier<I18nContentSchema>,
  args: GetTranslationsForArgs,
): Promise<Map<string, Record<string, string>>> {
  validateReadArgs(args.entityType, args.fields)

  if (args.entityIds.length === 0) {
    return new Map()
  }

  const fallbackToDefault = args.fallbackToDefault ?? false
  const locales = await resolveLocales(q, args.locale, fallbackToDefault)

  const rows = await q
    .select({
      entityId: translationValue.entityId,
      fieldKey: translationValue.fieldKey,
      locale: translationValue.locale,
      value: translationValue.value,
    })
    .from(translationValue)
    .where(
      and(
        inArray(translationValue.entityId, [...args.entityIds]),
        ...buildReadConditions(args.entityType, locales, args.fields),
      ),
    )

  const byEntity = new Map<string, TranslationRow[]>()
  for (const row of rows) {
    const list = byEntity.get(row.entityId) ?? []
    list.push(row)
    byEntity.set(row.entityId, list)
  }

  const result = new Map<string, Record<string, string>>()
  for (const entityId of args.entityIds) {
    const entityRows = byEntity.get(entityId) ?? []
    result.set(entityId, mergeLocaleRows(entityRows, args.locale, fallbackToDefault))
  }

  return result
}
