import React, { FC, useCallback, useRef, useState } from 'react'
import {
  Button,
  Modal,
  Notice,
  SelectControl,
  Spinner,
  TextControl,
} from '@wordpress/components'
import { __ } from '@wordpress/i18n'
import apiFetch from '@wordpress/api-fetch'
import { createInterpolateElement } from '@wordpress/element'

type PrinterScope = 'all' | 'mailbox' | 'other'

type PrinterAssignment = {
  scope: PrinterScope
  printerId: number
}

type PrinterChoice = {
  id: number
  scope: PrinterScope
  printerId: string
}

type AvailablePrinter = {
  id: number
  name: string
  state: string | null
}

type PrintingSetup = {
  subscriptionKeyConfigured: boolean
}

const SCOPE_VALUES: Array<PrinterScope> = ['all', 'mailbox', 'other']

const scopeLabel = (scope: PrinterScope): string => {
  switch (scope) {
    case 'mailbox':
      return __(
        'Home mailbox parcel – 100 x 100 mm',
        'posten-bring-checkout'
      )
    case 'other':
      return __('All others – 102 x 192 mm', 'posten-bring-checkout')
    default:
      return __('All products', 'posten-bring-checkout')
  }
}

const messageOf = (error: unknown, fallback: string): string => {
  const { code, message } = (error ?? {}) as { code?: unknown; message?: unknown }

  return typeof code === 'string' && typeof message === 'string' && message.trim()
    ? message
    : fallback
}

const asScope = (value: string): PrinterScope =>
  SCOPE_VALUES.find(scope => scope === value) ?? 'all'

const unusedScope = (choices: Array<PrinterChoice>): PrinterScope =>
  SCOPE_VALUES.find(scope => !choices.some(choice => choice.scope === scope)) ?? 'other'

export declare interface AutomaticPrintingProps {
  className?: string
}

const AutomaticPrinting: FC<AutomaticPrintingProps> = ({ className }) => {
  const [open, setOpen] = useState<boolean>(false)
  const [key, setKey] = useState<string>('')
  const [keyStored, setKeyStored] = useState<boolean>(false)
  const [choices, setChoices] = useState<Array<PrinterChoice>>([])
  const [printers, setPrinters] = useState<Array<AvailablePrinter>>([])
  const [loading, setLoading] = useState<boolean>(false)
  const [loaded, setLoaded] = useState<boolean>(false)
  const [saving, setSaving] = useState<boolean>(false)
  const [error, setError] = useState<string | undefined>(undefined)
  const nextChoiceId = useRef<number>(0)

  const load = useCallback(async () => {
    setLoading(true)
    setError(undefined)
    setLoaded(false)

    try {
      const setup = await apiFetch<PrintingSetup>({
        path: '/posten-bring-checkout/printing/setup',
      })
      const assignments = await apiFetch<Array<PrinterAssignment>>({
        path: '/posten-bring-checkout/printing/printers',
      })

      setKeyStored(setup.subscriptionKeyConfigured)
      setChoices(
        assignments.length > 0
          ? assignments.map(assignment => ({
              id: nextChoiceId.current++,
              scope: assignment.scope,
              printerId: String(assignment.printerId),
            }))
          : [{ id: nextChoiceId.current++, scope: 'all' as PrinterScope, printerId: '' }]
      )

      if (setup.subscriptionKeyConfigured) {
        setPrinters(
          await apiFetch<Array<AvailablePrinter>>({
            path: '/posten-bring-checkout/printing/available-printers',
          })
        )
      } else {
        setPrinters([])
      }

      setLoaded(true)
    } catch (error: unknown) {
      setError(
        messageOf(
          error,
          __(
            'The setup could not be loaded. Try opening the window again.',
            'posten-bring-checkout'
          )
        )
      )
    } finally {
      setLoading(false)
    }
  }, [])

  const openModal = () => {
    setOpen(true)
    load()
  }

  const closeModal = () => setOpen(false)

  const saveKey = async () => {
    setSaving(true)
    setError(undefined)

    try {
      await apiFetch({
        path: '/posten-bring-checkout/printing/setup',
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ subscriptionKey: key }),
      })
      setKey('')
    } catch {
      setError(
        __(
          'The key was not accepted. Check that you copied the whole key from PrintNode.',
          'posten-bring-checkout'
        )
      )
      return
    } finally {
      setSaving(false)
    }

    await load()
  }

  const savePrinters = async () => {
    setSaving(true)
    setError(undefined)

    const assignments: Array<PrinterAssignment> = choices
      .filter(choice => choice.printerId)
      .map(choice => ({
        scope: choice.scope,
        printerId: Number(choice.printerId),
      }))

    try {
      await apiFetch({
        path: '/posten-bring-checkout/printing/printers',
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(assignments),
      })
    } catch (error: unknown) {
      setError(
        messageOf(
          error,
          __(
            'The printers could not be saved. Please try again.',
            'posten-bring-checkout'
          )
        )
      )
      return
    } finally {
      setSaving(false)
    }

    closeModal()
  }

  const turnOff = async () => {
    setSaving(true)
    setError(undefined)

    try {
      await apiFetch({
        path: '/posten-bring-checkout/printing/printers',
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify([]),
      })
      await apiFetch({
        path: '/posten-bring-checkout/printing/setup',
        method: 'DELETE',
      })
    } catch {
      setError(
        __(
          'Automatic printing could not be turned off. Please try again.',
          'posten-bring-checkout'
        )
      )
      return
    } finally {
      setSaving(false)
    }

    await load()
  }

  const printerOptions = [
    { label: __('Select printer', 'posten-bring-checkout'), value: '' },
    ...printers.map(printer => ({
      label:
        printer.state === 'online'
          ? printer.name
          : `${printer.name} – ${__('offline', 'posten-bring-checkout')}`,
      value: String(printer.id),
    })),
  ]

  const scopeOptionsFor = (choice: PrinterChoice) =>
    SCOPE_VALUES.filter(
      scope => scope === choice.scope || !choices.some(it => it.scope === scope)
    ).map(scope => ({ label: scopeLabel(scope), value: scope }))

  const update = (choice: PrinterChoice, change: Partial<PrinterChoice>) =>
    setChoices(current =>
      current.map(it => (it === choice ? { ...it, ...change } : it))
    )

  const remove = (choice: PrinterChoice) =>
    setChoices(current => current.filter(it => it !== choice))

  const add = () =>
    setChoices(current => [
      ...current,
      {
        id: nextChoiceId.current++,
        scope: unusedScope(current),
        printerId: '',
      },
    ])

  return (
    <div className={className}>
      <Button
        onClick={openModal}
        __next40pxDefaultSize={true}
        variant="secondary"
        icon="printer"
      >
        {__('Automatic printing', 'posten-bring-checkout')}
      </Button>

      {open && (
        <Modal
          onRequestClose={closeModal}
          title={__('Automatic label printing', 'posten-bring-checkout')}
        >
          <div id="automatic-printing-container">
            {error && (
              <Notice status="error" isDismissible={false} className="mbs">
                {error}
              </Notice>
            )}

            <p>
              {createInterpolateElement(
                __(
                  'Labels can be printed at the packing station when you book transport, instead of you downloading a PDF. Printing goes through <a>PrintNode</a>, which is the bridge between us and a printer on your own network. You create an account with them, install a small program on the machine next to the printer, and paste your key here.',
                  'posten-bring-checkout'
                ),
                {
                  a: (
                    <a
                      href="https://www.printnode.com/"
                      target="_blank"
                      rel="noopener noreferrer"
                    />
                  ),
                }
              )}
            </p>

            <h3>{__('Key from PrintNode', 'posten-bring-checkout')}</h3>

            {keyStored ? (
              <p>
                {__('A key is saved.', 'posten-bring-checkout')}{' '}
                <Button variant="link" onClick={() => setKeyStored(false)}>
                  {__('Change key', 'posten-bring-checkout')}
                </Button>
              </p>
            ) : (
              <TextControl
                label={__('Subscription key', 'posten-bring-checkout')}
                help={__(
                  'You will find it under API Keys in PrintNode.',
                  'posten-bring-checkout'
                )}
                placeholder={__('Paste the key', 'posten-bring-checkout')}
                type="password"
                autoComplete="off"
                __next40pxDefaultSize={true}
                value={key}
                onChange={setKey}
              />
            )}

            <h3>{__('Printers', 'posten-bring-checkout')}</h3>

            {!keyStored ? (
              <p>
                {__(
                  'Your printers appear here once the key is saved.',
                  'posten-bring-checkout'
                )}
              </p>
            ) : loading ? (
              <p>
                <Spinner />{' '}
                {__(
                  'Fetching printers from PrintNode',
                  'posten-bring-checkout'
                )}
              </p>
            ) : printers.length === 0 ? (
              <Notice status="warning" isDismissible={false}>
                {__(
                  'PrintNode does not see any printers. Check that the machine next to the printer is on and that the PrintNode program is running.',
                  'posten-bring-checkout'
                )}
              </Notice>
            ) : (
              <>
                {choices.map((choice, index) => (
                  <div className="automatic-printing__row" key={choice.id}>
                    <SelectControl
                      label={__('Printer', 'posten-bring-checkout')}
                      hideLabelFromVision={index > 0}
                      __next40pxDefaultSize={true}
                      options={printerOptions}
                      value={choice.printerId}
                      onChange={(value: string) =>
                        update(choice, { printerId: value })
                      }
                    />
                    <SelectControl
                      label={__('Prints', 'posten-bring-checkout')}
                      hideLabelFromVision={index > 0}
                      __next40pxDefaultSize={true}
                      options={scopeOptionsFor(choice)}
                      value={choice.scope}
                      onChange={(value: string) =>
                        update(choice, { scope: asScope(value) })
                      }
                    />
                    {choices.length > 1 && (
                      <Button
                        icon="trash"
                        label={__('Remove printer', 'posten-bring-checkout')}
                        onClick={() => remove(choice)}
                      />
                    )}
                  </div>
                ))}

                {choices.length < SCOPE_VALUES.length && (
                  <div className="automatic-printing__add">
                    <Button variant="link" icon="plus" onClick={add}>
                      {__('Add printer', 'posten-bring-checkout')}
                    </Button>
                  </div>
                )}
              </>
            )}

            <div className="automatic-printing__actions mtm">
              <Button
                variant="primary"
                __next40pxDefaultSize={true}
                disabled={saving || loading || (keyStored ? !loaded : !key.trim())}
                onClick={() => (keyStored ? savePrinters() : saveKey())}
              >
                {__('Save', 'posten-bring-checkout')}
              </Button>

              {keyStored && (
                <Button variant="link" disabled={saving} onClick={turnOff}>
                  {__('Turn off automatic printing', 'posten-bring-checkout')}
                </Button>
              )}
            </div>
          </div>
        </Modal>
      )}
    </div>
  )
}

export default AutomaticPrinting
