import type { PostImportEditableDraft,
	      PostImportResultRow,
	      PostImportRow } from '@/lib/postImportTypes'

const THUMBNAIL_MISSING_WARNING = 'サムネールなし'

export const hasThumbnailBaseValue = (value: unknown): boolean =>
    typeof value === 'string' && value.trim () !== ''

export const hasVideoTag = (value: unknown): boolean =>
    typeof value === 'string'
    && value.split (/\s+/).includes ('動画')

export const isExistingSkipRow = (row: PostImportRow): boolean =>
    row.skipReason === 'existing'


export const isManualSkipRow = (row: PostImportRow): boolean =>
    row.skipReason === 'manual'


const hasSkipReason = (row: PostImportRow): boolean =>
    row.skipReason != null

export const compactMessageRecord = (
    messages: Record<string, string[]>,
): Record<string, string[]> =>
    Object.fromEntries (
	Object.entries (messages).filter (([, values]) => values.length > 0))


export const hasErrorMessages = (
    messages: Record<string, string[]>,
): boolean =>
    Object.values (messages).some (values => values.length > 0)


const hasValidationErrors = (row: PostImportRow): boolean =>
    hasErrorMessages (row.validationErrors ?? { })

const isRecoverableRow = (row: PostImportRow): boolean =>
    row.recoverable === true

const isRepairableImportStatus = (row: PostImportRow): boolean =>
    isRecoverableRow (row)
    && (row.importStatus === 'failed' || row.importStatus === 'pending')


export const isNonRecoverableFailedRow = (row: PostImportRow): boolean =>
    row.importStatus === 'failed' && row.recoverable !== true


export const isTerminalRow = (row: PostImportRow): boolean =>
    row.importStatus === 'created'
    || row.importStatus === 'skipped'
    || isNonRecoverableFailedRow (row)


export const isCompletedReviewRow = (row: PostImportRow): boolean =>
    row.importStatus === 'created'
    || row.importStatus === 'skipped'
    || hasSkipReason (row)


export const validatableImportRows = (rows: PostImportRow[]): PostImportRow[] =>
    rows.filter (row => !(hasSkipReason (row)) && !(isTerminalRow (row)))

const buildResetSnapshot = (row: PostImportRow) => ({
    url: row.url,
    attributes: { ...row.attributes },
    displayTags: row.displayTags?.map (tag => ({
	name: tag.name,
	category: tag.category,
	sectionLiterals: tag.sectionLiterals == null ? undefined : [...tag.sectionLiterals] })) ?? [],
    provenance: { ...row.provenance },
    tagSources: {
	automatic: row.tagSources?.automatic ?? '',
	manual: row.tagSources?.manual ?? '' },
    fieldWarnings: Object.fromEntries (
	Object.entries (row.fieldWarnings).map (([key, values]) => [key, [...values]])),
    baseWarnings: [...row.baseWarnings],
    metadataUrl: row.metadataUrl })

const deduped = (values: string[]): string[] =>
    [...new Set (values)]


const thumbnailWarnings = (row: PostImportRow): string[] => {
    const current = row.fieldWarnings.thumbnailBase ?? []
    const others = current.filter (message => message !== THUMBNAIL_MISSING_WARNING)
    return hasThumbnailBaseValue (row.attributes.thumbnailBase) || row.thumbnailFile != null
        ? others
        : deduped ([...others, THUMBNAIL_MISSING_WARNING])
}


export const applyThumbnailWarning = (row: PostImportRow): PostImportRow => ({
    ...row,
    fieldWarnings: compactMessageRecord ({
	...row.fieldWarnings,
	thumbnailBase: thumbnailWarnings (row) }) })


export const applyThumbnailWarnings = (rows: PostImportRow[]): PostImportRow[] =>
    rows.map (row => applyThumbnailWarning (row))


export const processableImportRows = (rows: PostImportRow[]): PostImportRow[] =>
    validatableImportRows (rows).filter (row => {
      if (row.status === 'pending')
        return false
      if (row.importStatus === 'created')
        return false
      if (row.importStatus === 'skipped')
        return false
      if (row.importStatus === 'failed')
        return false
      if (hasValidationErrors (row))
        return false
      return row.importStatus == null || row.importStatus === 'pending'
    })

export const creatableImportRows = (rows: PostImportRow[]): PostImportRow[] =>
    processableImportRows (rows).filter (row => !(hasSkipReason (row)))


export const reviewSummaryCounts = (rows: PostImportRow[]) => ({
    creatable: rows.filter (row => creatableImportRows ([row]).length > 0).length,
    manualSkipped: rows.filter (row => isManualSkipRow (row)).length,
    existingSkipped: rows.filter (row => isExistingSkipRow (row)).length,
    pendingOrError: rows.filter (row =>
        !(isCompletedReviewRow (row))
        && creatableImportRows ([row]).length === 0).length })


export const resultSummaryCounts = (rows: PostImportRow[]) =>
    rows.reduce (
        (counts, row) => {
          if (row.importStatus === 'created')
            {
              ++counts.created
              return counts
            }
          if (row.importStatus === 'skipped')
            {
              ++counts.skipped
              return counts
            }
          if (row.importStatus === 'failed'
              || (row.recoverable === true && row.importStatus === 'pending'))
            ++counts.failed
          return counts
        },
        { created: 0, skipped: 0, failed: 0 })


export const resultRepairMode = (
    rows: PostImportRow[],
): 'all' | 'failed' =>
    rows.some (row => hasValidationErrors (row) || isRepairableImportStatus (row))
    ? 'failed'
    : 'all'


export const canEditReviewRow = (row: PostImportRow): boolean =>
    !(hasSkipReason (row)
      || row.importStatus === 'created'
      || row.importStatus === 'skipped'
      || (row.importStatus === 'failed' && row.recoverable !== true))


export const canEditResultRow = (row: PostImportRow): boolean =>
    row.skipReason == null
    && row.recoverable === true
    && (row.importStatus === 'failed'
        || (row.importStatus === 'pending' && hasValidationErrors (row)))


export const canRetryResultRow = (row: PostImportRow): boolean =>
    row.skipReason == null
    && row.recoverable === true
    && (row.importStatus === 'failed'
        || (row.importStatus === 'pending' && !(hasValidationErrors (row))))


export const resultRowMessages = (row: PostImportRow): string[] =>
    [...new Set ([
        ...Object.values (row.validationErrors ?? { }).flat (),
        ...Object.values (row.importErrors ?? { }).flat ()])]


export const resultRowWarnings = (row: PostImportRow): string[] =>
    [...new Set ([
        ...Object.values (row.fieldWarnings ?? { }).flat (),
        ...row.baseWarnings])]


const isManualChange = (
    current: unknown,
    next:    string,
): boolean =>
    next !== String (current ?? '')


export const buildNextEditedRow = (
    editingRow:  PostImportRow,
    draft:       PostImportEditableDraft,
    urlChanged:  boolean,
): PostImportRow => {
    const nextProvenance = { ...editingRow.provenance }
    const nextAttributes = { ...editingRow.attributes }
    const nextTagSources = {
	automatic: editingRow.tagSources?.automatic ?? '',
	manual: editingRow.tagSources?.manual ?? '' }
    const draftFields = [
	['title', draft.title],
	['thumbnailBase', draft.thumbnailBase],
	['originalCreatedFrom', draft.originalCreatedFrom],
	['originalCreatedBefore', draft.originalCreatedBefore],
	['duration', draft.duration],
	['parentPostIds', draft.parentPostIds]] as const
    draftFields.forEach (([field, value]) => {
	nextAttributes[field] = value
	nextProvenance[field] =
	    isManualChange (editingRow.attributes[field], value)
	    ? 'manual'
	    : (editingRow.provenance[field] ?? 'automatic')
    })
    nextAttributes.tags = draft.tags
    if (isManualChange (editingRow.attributes.tags, draft.tags))
      {
	nextProvenance.tags = 'manual'
	nextTagSources.manual = draft.tags
      }
    else
      {
	nextProvenance.tags = editingRow.provenance.tags ?? 'automatic'
	nextTagSources.manual = editingRow.tagSources?.manual ?? ''
      }

    return {
	...editingRow,
	url: draft.url,
	attributes: nextAttributes,
	displayTags:
	    editingRow.displayTags?.map (tag => ({
		name: tag.name,
		category: tag.category,
		sectionLiterals: tag.sectionLiterals == null ? undefined : [...tag.sectionLiterals] })),
	thumbnailFile: draft.thumbnailFile,
	provenance: {
	    ...nextProvenance,
	    url: urlChanged ? 'manual' : (editingRow.provenance.url ?? 'manual') },
	tagSources: nextTagSources,
	importStatus: editingRow.importStatus === 'created' ? 'created' : 'pending',
	importErrors: undefined }
}


export const hasExactSourceRows = (
    expected: number[],
    actual:   Array<{ sourceRow: number }>,
): boolean => {
    if (expected.length !== actual.length)
      return false

    const expectedSorted = [...expected].sort ((a, b) => a - b)
    const actualSorted = actual.map (row => row.sourceRow).sort ((a, b) => a - b)
    return expectedSorted.every ((value, index) => value === actualSorted[index])
}


export const replaceImportRow = (
    rows:      PostImportRow[],
    nextRow:   PostImportRow,
): PostImportRow[] =>
    rows.map (row => row.sourceRow === nextRow.sourceRow ? nextRow : row)


export const mergeValidatedImportRow = (
    rows:      PostImportRow[],
    validated: PostImportRow,
): PostImportRow[] => {
    const current = rows.find (row => row.sourceRow === validated.sourceRow)
    if (current == null)
      return rows

    const [merged] = mergeValidatedImportRows ([current], [validated])
    return merged == null ? rows : replaceImportRow (rows, merged)
}


export const mergeValidatedImportRows = (
    current:   PostImportRow[],
    validated: PostImportRow[],
): PostImportRow[] => {
    const validatedMap = new Map (validated.map (row => [row.sourceRow, row]))
    return current.map (previous => {
      if (
          previous.importStatus === 'created'
          || previous.importStatus === 'skipped'
          || previous.importStatus === 'failed')
        return previous

      const row = validatedMap.get (previous.sourceRow)
      if (row == null)
        return previous

      const fieldWarnings =
          hasErrorMessages (row.fieldWarnings) || row.metadataUrl !== previous.metadataUrl
          ? { ...row.fieldWarnings }
          : { ...previous.fieldWarnings }
      for (const [field, origin] of Object.entries (row.provenance))
        {
          if (origin === 'manual')
            delete fieldWarnings[field]
        }

      return {
          ...previous,
          url: row.url,
          attributes: row.attributes,
          displayTags:
              row.displayTags?.map (tag => ({
                  name: tag.name,
                  category: tag.category,
                  sectionLiterals:
                      tag.sectionLiterals == null ? undefined : [...tag.sectionLiterals] })),
          provenance: row.provenance,
          tagSources: row.tagSources,
          skipReason: row.skipReason,
          existingPostId: row.existingPostId,
          existingPost: row.existingPost,
          fieldWarnings,
          baseWarnings:
              row.baseWarnings.length > 0 || row.metadataUrl !== previous.metadataUrl
              ? row.baseWarnings
              : previous.baseWarnings,
          validationErrors: row.validationErrors,
          status: row.status,
          metadataUrl: row.metadataUrl,
          resetSnapshot:
              row.metadataUrl !== previous.metadataUrl
              ? buildResetSnapshot (row)
              : previous.resetSnapshot }
    })
}


export const mergeImportResults = (
    rows:    PostImportRow[],
    results: PostImportResultRow[],
): PostImportRow[] => {
    const resultMap = new Map (results.map (row => [row.sourceRow, row]))
    return rows.map (row => {
      const result = resultMap.get (row.sourceRow)
      if (result == null)
        return row

      switch (result.status)
        {
        case 'created':
          return {
              ...row,
              importStatus: 'created',
              recoverable: undefined,
              skipReason: undefined,
              createdPostId: result.post.id,
              existingPostId: undefined,
              existingPost: undefined,
              fieldWarnings: compactMessageRecord (
		  result.fieldWarnings ?? row.fieldWarnings),
              baseWarnings: result.baseWarnings ?? row.baseWarnings,
              importErrors: compactMessageRecord (result.errors ?? { }) }
        case 'skipped':
          return {
              ...row,
              importStatus: 'skipped',
              recoverable: undefined,
              skipReason: 'existing',
              createdPostId: undefined,
              existingPostId: result.existingPostId,
              existingPost: result.existingPost ?? row.existingPost,
              fieldWarnings: compactMessageRecord (
		  result.fieldWarnings ?? row.fieldWarnings),
              baseWarnings: result.baseWarnings ?? row.baseWarnings,
              importErrors: compactMessageRecord (result.errors ?? { }) }
        case 'failed':
          return {
              ...row,
              importStatus: 'failed',
              recoverable: result.recoverable === true ? true : undefined,
              skipReason: undefined,
              createdPostId: undefined,
              existingPostId: undefined,
              existingPost: undefined,
              fieldWarnings: compactMessageRecord (
		  result.fieldWarnings ?? row.fieldWarnings),
              baseWarnings: result.baseWarnings ?? row.baseWarnings,
              importErrors: compactMessageRecord (result.errors ?? { }) }
        }
    })
}


export const retryImportRow = (
    rows:      PostImportRow[],
    sourceRow: number,
): PostImportRow[] =>
    rows.map (row =>
        row.sourceRow === sourceRow
        && row.importStatus === 'failed'
        && row.recoverable === true
        ? { ...row, importStatus: 'pending', importErrors: undefined }
        : row)

export const initialisePreviewRows = (rows: PostImportRow[]): PostImportRow[] =>
    applyThumbnailWarnings (
	rows.map (row => ({
	    ...row,
	    resetSnapshot: buildResetSnapshot (row) })))
