import {
  Box,
  Button,
  Divider,
  Group,
  NumberInput,
  Select,
  SegmentedControl,
  SimpleGrid,
  Textarea,
  Stack,
  TextInput,
  Typography,
  notifier
} from '@tidbcloud/uikit'
import { MRT_ColumnDef, ProTable } from '@tidbcloud/uikit/biz'
import { IconEdit03, IconRefreshCcw01 } from '@tidbcloud/uikit/icons'
import { useMemo, useState } from 'react'

import {
  calculateReservedCapacity,
  createTiflashDataDisk,
  createTikvDataDisk,
  createTikvRaftDisk,
  DATA_DISK_CAPACITY_STEP_GIB,
  DATA_DISK_THROUGHPUT_MBPS_MAX,
  DEFAULT_RESERVED_CAPACITY_CALCULATOR_INPUT,
  getReservedCapacityEstimatedVcpu,
  getReservedCapacityTopologySummary,
  RESERVED_CAPACITY_FIXED_ASSUMPTIONS,
  RESERVED_CAPACITY_COMPONENT_MIN_NODES,
  RESERVED_CAPACITY_COMPONENT_ORDER,
  RESERVED_CAPACITY_COMPONENT_SPEC_OPTIONS,
  RESERVED_CAPACITY_SPEC_RESOURCE,
  ReservedCapacityCalculationResult,
  ReservedCapacityCalculatorInput,
  ReservedCapacityComponentPlan,
  ReservedCapacityComponentType,
  ReservedCapacityDiskPlan,
  ReservedCapacitySpecName,
  TIFLASH_DATA_DISK_MAX_GIB,
  TIFLASH_DATA_DISK_IOPS_MAX,
  TIFLASH_DATA_DISK_IOPS_MIN,
  TIFLASH_DATA_DISK_MIN_GIB,
  TIKV_DATA_DISK_IOPS_MAX,
  TIKV_DATA_DISK_IOPS_MIN,
  TIKV_DATA_DISK_LIMITS_GIB
} from './calculator'

export type PremiumReservedCapacityCalculatorFormValues =
  ReservedCapacityCalculatorInput & {
    description: string
    name: string
  }

type PremiumReservedCapacityCalculatorProps = {
  loading?: boolean
  onCancel: () => void
  onSave: (
    values: PremiumReservedCapacityCalculatorFormValues,
    calculation: ReservedCapacityCalculationResult
  ) => Promise<void>
}

type CalculatorFormErrors = Partial<
  Record<keyof PremiumReservedCapacityCalculatorFormValues, string>
>
type TopologyField =
  | 'dataDiskIops'
  | 'dataDiskSize'
  | 'dataDiskThroughput'
  | 'nodeQuantity'
  | 'spec'
type TopologyErrors = Record<string, string>

const DEFAULT_FORM_VALUES: PremiumReservedCapacityCalculatorFormValues = {
  ...DEFAULT_RESERVED_CAPACITY_CALCULATOR_INPUT,
  description: '',
  name: ''
}

const isTiflashComponent = (componentType: ReservedCapacityComponentType) =>
  componentType === 'TIFLASH_COMPUTE' || componentType === 'TIFLASH_WRITE'

const isDataDiskEditable = (componentType: ReservedCapacityComponentType) =>
  componentType === 'TIKV' || isTiflashComponent(componentType)

const cloneComponents = (components: ReservedCapacityComponentPlan[]) =>
  components.map((component) => ({
    ...component,
    disks: component.disks?.map((disk) => ({ ...disk }))
  }))

const getTopologyErrorKey = (
  componentType: ReservedCapacityComponentType,
  field: TopologyField
) => `${componentType}.${field}`

const getDataDisk = (component: ReservedCapacityComponentPlan) =>
  component.disks?.find((disk) => disk.diskType === 'DATA')

const getSpecResource = (spec: ReservedCapacitySpecName) =>
  RESERVED_CAPACITY_SPEC_RESOURCE[spec]

const createCalculationWithComponents = (
  calculation: ReservedCapacityCalculationResult,
  components: ReservedCapacityComponentPlan[]
): ReservedCapacityCalculationResult => ({
  ...calculation,
  components,
  estimatedVcpu: getReservedCapacityEstimatedVcpu(components),
  topologySummary: getReservedCapacityTopologySummary(components)
})

const isAllowedSpec = (
  componentType: ReservedCapacityComponentType,
  spec: string
): spec is ReservedCapacitySpecName =>
  RESERVED_CAPACITY_COMPONENT_SPEC_OPTIONS[componentType].includes(
    spec as ReservedCapacitySpecName
  )

const applySpecToComponent = (
  component: ReservedCapacityComponentPlan,
  spec: ReservedCapacitySpecName
): ReservedCapacityComponentPlan => {
  const resource = getSpecResource(spec)
  const nextComponent: ReservedCapacityComponentPlan = {
    ...component,
    spec,
    vcpu: resource.vcpu,
    memoryGib: resource.memoryGib
  }

  if (component.componentType === 'TIKV') {
    const currentDataDisk = getDataDisk(component)
    const maxSize = TIKV_DATA_DISK_LIMITS_GIB[spec as '8c32g' | '16c64g']
    const sizeGib = Math.min(currentDataDisk?.sizeGib || 100, maxSize)
    const defaultDataDisk = createTikvDataDisk(sizeGib)

    return {
      ...nextComponent,
      disks: [
        {
          ...defaultDataDisk,
          iops: currentDataDisk?.iops ?? defaultDataDisk.iops,
          throughputMbps:
            currentDataDisk?.throughputMbps ?? defaultDataDisk.throughputMbps
        },
        createTikvRaftDisk()
      ]
    }
  }

  if (isTiflashComponent(component.componentType)) {
    const currentDataDisk = getDataDisk(component)
    const sizeGib = Math.min(
      Math.max(
        currentDataDisk?.sizeGib || TIFLASH_DATA_DISK_MIN_GIB,
        TIFLASH_DATA_DISK_MIN_GIB
      ),
      TIFLASH_DATA_DISK_MAX_GIB
    )
    const defaultDataDisk = createTiflashDataDisk(sizeGib)

    return {
      ...nextComponent,
      disks: [
        {
          ...defaultDataDisk,
          iops: currentDataDisk?.iops ?? defaultDataDisk.iops,
          throughputMbps:
            currentDataDisk?.throughputMbps ?? defaultDataDisk.throughputMbps
        }
      ]
    }
  }

  return nextComponent
}

const renderDiskSummary = (disk: ReservedCapacityDiskPlan) =>
  `${disk.iops} IOPS, ${disk.throughputMbps} Mbps`

const TOPOLOGY_TABLE_CELL_PROPS = {
  sx: {
    padding: '8px 10px !important'
  }
}

const normalizePositiveNumber = (value: number | undefined, fallback: number) =>
  typeof value === 'number' && value > 0 ? value : fallback

const normalizeInput = (
  values: Partial<PremiumReservedCapacityCalculatorFormValues>
): ReservedCapacityCalculatorInput => ({
  qps: normalizePositiveNumber(values.qps, DEFAULT_FORM_VALUES.qps),
  workloadPattern:
    values.workloadPattern || DEFAULT_FORM_VALUES.workloadPattern,
  latencyRequirement:
    values.latencyRequirement || DEFAULT_FORM_VALUES.latencyRequirement,
  customerDataSizeGb:
    values.customerDataSizeGb || DEFAULT_FORM_VALUES.customerDataSizeGb
})

const renderDisks = (component: ReservedCapacityComponentPlan) => {
  if (!component.disks?.length) {
    return '-'
  }

  return component.disks
    .map(
      (disk) =>
        `${disk.diskType}: ${disk.sizeGib} GiB, ${renderDiskSummary(disk)}`
    )
    .join('; ')
}

const normalizeNumber = (value: string | number) => Number(value) || 0

const isStepAligned = (value: number, step: number) =>
  Number.isFinite(value) && Number.isInteger(value) && value % step === 0

const isPositiveInteger = (value: number) =>
  Number.isFinite(value) && Number.isInteger(value) && value > 0

const isTikvDataDiskSizeValid = (sizeGib: number, maxSizeGib: number) =>
  sizeGib >= 100 &&
  sizeGib <= maxSizeGib &&
  (sizeGib === maxSizeGib ||
    isStepAligned(sizeGib, DATA_DISK_CAPACITY_STEP_GIB))

const isTiflashDataDiskSizeValid = (sizeGib: number) =>
  sizeGib >= TIFLASH_DATA_DISK_MIN_GIB &&
  sizeGib <= TIFLASH_DATA_DISK_MAX_GIB &&
  (sizeGib === TIFLASH_DATA_DISK_MAX_GIB ||
    isStepAligned(sizeGib, DATA_DISK_CAPACITY_STEP_GIB))

const AssumptionItem = ({ label, value }: { label: string; value: number }) => (
  <Stack gap={2}>
    <Typography variant="label-sm" c="carbon.6">
      {label}
    </Typography>
    <Typography variant="body-lg">{value}</Typography>
  </Stack>
)

export default function PremiumReservedCapacityCalculator({
  loading,
  onCancel,
  onSave
}: PremiumReservedCapacityCalculatorProps) {
  const [formValues, setFormValues] =
    useState<PremiumReservedCapacityCalculatorFormValues>(DEFAULT_FORM_VALUES)
  const [formErrors, setFormErrors] = useState<CalculatorFormErrors>({})
  const [isEditingTopology, setIsEditingTopology] = useState(false)
  const [topologyComponents, setTopologyComponents] =
    useState<ReservedCapacityComponentPlan[]>()
  const [topologyErrors, setTopologyErrors] = useState<TopologyErrors>({})

  const defaultCalculation = useMemo(
    () => calculateReservedCapacity(normalizeInput(formValues)),
    [formValues]
  )
  const calculation = useMemo(
    () =>
      isEditingTopology && topologyComponents
        ? createCalculationWithComponents(
            defaultCalculation,
            topologyComponents
          )
        : defaultCalculation,
    [defaultCalculation, isEditingTopology, topologyComponents]
  )

  const clearTopologyError = (
    componentType: ReservedCapacityComponentType,
    field: TopologyField
  ) => {
    setTopologyErrors((prev) => {
      const nextErrors = { ...prev }
      delete nextErrors[getTopologyErrorKey(componentType, field)]
      return nextErrors
    })
  }

  const updateTopologyComponent = (
    componentType: ReservedCapacityComponentType,
    updater: (
      component: ReservedCapacityComponentPlan
    ) => ReservedCapacityComponentPlan
  ) => {
    setTopologyComponents((prev) =>
      cloneComponents(prev || defaultCalculation.components).map((component) =>
        component.componentType === componentType
          ? updater(component)
          : component
      )
    )
  }

  const updateDataDisk = (
    componentType: ReservedCapacityComponentType,
    updater: (disk: ReservedCapacityDiskPlan) => ReservedCapacityDiskPlan
  ) => {
    updateTopologyComponent(componentType, (component) => {
      const currentDataDisk = getDataDisk(component)
      const fallbackSizeGib =
        componentType === 'TIKV' ? 100 : TIFLASH_DATA_DISK_MIN_GIB
      const baseDataDisk =
        currentDataDisk ||
        (componentType === 'TIKV'
          ? createTikvDataDisk(fallbackSizeGib)
          : createTiflashDataDisk(fallbackSizeGib))
      const dataDisk = updater(baseDataDisk)

      return {
        ...component,
        disks:
          componentType === 'TIKV'
            ? [dataDisk, createTikvRaftDisk()]
            : [dataDisk]
      }
    })
  }

  const handleEditTopology = () => {
    setTopologyComponents(cloneComponents(defaultCalculation.components))
    setTopologyErrors({})
    setIsEditingTopology(true)
  }

  const handleResetTopology = () => {
    setTopologyComponents(cloneComponents(defaultCalculation.components))
    setTopologyErrors({})
  }

  const handleSpecChange = (
    componentType: ReservedCapacityComponentType,
    spec: string | null
  ) => {
    if (!spec || !isAllowedSpec(componentType, spec)) {
      return
    }

    updateTopologyComponent(componentType, (component) =>
      applySpecToComponent(component, spec)
    )
    clearTopologyError(componentType, 'spec')
    clearTopologyError(componentType, 'dataDiskSize')
  }

  const handleNodeQuantityChange = (
    componentType: ReservedCapacityComponentType,
    value: string | number
  ) => {
    updateTopologyComponent(componentType, (component) => ({
      ...component,
      nodeQuantity: normalizeNumber(value)
    }))
    clearTopologyError(componentType, 'nodeQuantity')
  }

  const handleDataDiskSizeChange = (
    componentType: ReservedCapacityComponentType,
    value: string | number
  ) => {
    const sizeGib = normalizeNumber(value)

    updateDataDisk(componentType, (disk) => ({
      ...disk,
      sizeGib
    }))
    clearTopologyError(componentType, 'dataDiskSize')
  }

  const handleDataDiskIopsChange = (
    componentType: ReservedCapacityComponentType,
    value: string | number
  ) => {
    updateDataDisk(componentType, (disk) => ({
      ...disk,
      iops: normalizeNumber(value)
    }))
    clearTopologyError(componentType, 'dataDiskIops')
  }

  const handleDataDiskThroughputChange = (
    componentType: ReservedCapacityComponentType,
    value: string | number
  ) => {
    updateDataDisk(componentType, (disk) => ({
      ...disk,
      throughputMbps: normalizeNumber(value)
    }))
    clearTopologyError(componentType, 'dataDiskThroughput')
  }

  const renderSpecCell = (component: ReservedCapacityComponentPlan) => {
    const options =
      RESERVED_CAPACITY_COMPONENT_SPEC_OPTIONS[component.componentType]

    if (!isEditingTopology || options.length === 1) {
      return component.spec
    }

    return (
      <Select
        value={component.spec}
        data={options.map((spec) => ({ label: spec, value: spec }))}
        error={
          topologyErrors[getTopologyErrorKey(component.componentType, 'spec')]
        }
        onChange={(value) => handleSpecChange(component.componentType, value)}
        style={{ width: 132 }}
      />
    )
  }

  const renderNodeCell = (component: ReservedCapacityComponentPlan) => {
    if (!isEditingTopology || component.componentType === 'PD') {
      return component.nodeQuantity
    }

    return (
      <NumberInput
        value={component.nodeQuantity}
        min={RESERVED_CAPACITY_COMPONENT_MIN_NODES[component.componentType]}
        step={component.componentType === 'TIKV' ? 3 : 1}
        decimalScale={0}
        allowDecimal={false}
        error={
          topologyErrors[
            getTopologyErrorKey(component.componentType, 'nodeQuantity')
          ]
        }
        onChange={(value) =>
          handleNodeQuantityChange(component.componentType, value)
        }
        style={{ width: 120 }}
      />
    )
  }

  const renderDiskCell = (component: ReservedCapacityComponentPlan) => {
    if (!isEditingTopology || !isDataDiskEditable(component.componentType)) {
      return renderDisks(component)
    }

    const dataDisk = getDataDisk(component)
    const isTikv = component.componentType === 'TIKV'
    const maxSize = isTikv
      ? TIKV_DATA_DISK_LIMITS_GIB[
          component.spec as keyof typeof TIKV_DATA_DISK_LIMITS_GIB
        ] || TIKV_DATA_DISK_LIMITS_GIB['8c32g']
      : TIFLASH_DATA_DISK_MAX_GIB
    const minSize = isTikv ? 100 : TIFLASH_DATA_DISK_MIN_GIB
    const maxIops = isTikv
      ? TIKV_DATA_DISK_IOPS_MAX
      : TIFLASH_DATA_DISK_IOPS_MAX
    const minIops = isTikv
      ? TIKV_DATA_DISK_IOPS_MIN
      : TIFLASH_DATA_DISK_IOPS_MIN

    return (
      <Box style={{ minWidth: 520 }}>
        <Typography variant="label-sm" c="carbon.6" mb={4}>
          DATA
        </Typography>
        <SimpleGrid cols={{ base: 1, lg: 3 }} spacing={8}>
          <NumberInput
            label="Size"
            value={dataDisk?.sizeGib || 0}
            min={minSize}
            max={maxSize}
            step={DATA_DISK_CAPACITY_STEP_GIB}
            decimalScale={0}
            allowDecimal={false}
            rightAddon="GiB"
            error={
              topologyErrors[
                getTopologyErrorKey(component.componentType, 'dataDiskSize')
              ]
            }
            onChange={(value) =>
              handleDataDiskSizeChange(component.componentType, value)
            }
          />
          <NumberInput
            label="IOPS"
            value={dataDisk?.iops || 0}
            min={minIops}
            max={maxIops}
            step={1}
            decimalScale={0}
            allowDecimal={false}
            error={
              topologyErrors[
                getTopologyErrorKey(component.componentType, 'dataDiskIops')
              ]
            }
            onChange={(value) =>
              handleDataDiskIopsChange(component.componentType, value)
            }
          />
          <NumberInput
            label="Throughput"
            value={dataDisk?.throughputMbps || 0}
            min={1}
            max={DATA_DISK_THROUGHPUT_MBPS_MAX}
            step={1}
            decimalScale={0}
            allowDecimal={false}
            rightAddon="Mbps"
            error={
              topologyErrors[
                getTopologyErrorKey(
                  component.componentType,
                  'dataDiskThroughput'
                )
              ]
            }
            onChange={(value) =>
              handleDataDiskThroughputChange(component.componentType, value)
            }
          />
        </SimpleGrid>
        {isTikv && (
          <Typography variant="body-sm" c="carbon.6" mt={6}>
            RAFTLOG: 20 GiB, 3000 IOPS, 125 Mbps
          </Typography>
        )}
      </Box>
    )
  }

  const getCenteredTopologyCellProps = () => ({
    sx: {
      ...TOPOLOGY_TABLE_CELL_PROPS.sx,
      verticalAlign: 'middle'
    }
  })

  const componentColumns: MRT_ColumnDef<ReservedCapacityComponentPlan>[] = [
    {
      header: 'Component',
      accessorKey: 'componentType',
      mantineTableBodyCellProps: getCenteredTopologyCellProps,
      size: 150
    },
    {
      header: 'Spec',
      accessorKey: 'spec',
      Cell: ({ row }) => renderSpecCell(row.original),
      mantineTableBodyCellProps: getCenteredTopologyCellProps,
      size: 140
    },
    {
      header: 'Nodes',
      accessorKey: 'nodeQuantity',
      Cell: ({ row }) => renderNodeCell(row.original),
      mantineTableBodyCellProps: getCenteredTopologyCellProps,
      size: 130
    },
    {
      header: 'Total vCPU',
      accessorFn: (record) => record.nodeQuantity * record.vcpu,
      mantineTableBodyCellProps: getCenteredTopologyCellProps,
      size: 110
    },
    {
      id: 'disks',
      header: 'Disks',
      accessorFn: (record) => renderDisks(record),
      Cell: ({ row }) => renderDiskCell(row.original),
      mantineTableBodyCellProps: getCenteredTopologyCellProps,
      size: 560
    }
  ]

  const validateTopology = (components: ReservedCapacityComponentPlan[]) => {
    const nextErrors: TopologyErrors = {}

    RESERVED_CAPACITY_COMPONENT_ORDER.forEach((componentType) => {
      const component = components.find(
        (item) => item.componentType === componentType
      )

      if (!component) {
        nextErrors[getTopologyErrorKey(componentType, 'spec')] =
          'Component is required'
        return
      }

      if (!isAllowedSpec(componentType, component.spec)) {
        nextErrors[getTopologyErrorKey(componentType, 'spec')] =
          'Unsupported spec'
      }

      const minNodes = RESERVED_CAPACITY_COMPONENT_MIN_NODES[componentType]
      if (
        !Number.isInteger(component.nodeQuantity) ||
        component.nodeQuantity < minNodes
      ) {
        nextErrors[
          getTopologyErrorKey(componentType, 'nodeQuantity')
        ] = `Nodes must be at least ${minNodes}`
      }

      if (componentType === 'PD' && component.nodeQuantity !== 3) {
        nextErrors[getTopologyErrorKey(componentType, 'nodeQuantity')] =
          'PD nodes must be 3'
      }

      if (componentType === 'TIKV' && component.nodeQuantity % 3 !== 0) {
        nextErrors[getTopologyErrorKey(componentType, 'nodeQuantity')] =
          'TiKV nodes must be a multiple of 3'
      }

      if (componentType === 'TIKV') {
        const dataDisk = getDataDisk(component)
        const maxSize =
          TIKV_DATA_DISK_LIMITS_GIB[
            component.spec as keyof typeof TIKV_DATA_DISK_LIMITS_GIB
          ] || TIKV_DATA_DISK_LIMITS_GIB['8c32g']

        if (!dataDisk) {
          nextErrors[getTopologyErrorKey(componentType, 'dataDiskSize')] =
            'DATA disk is required'
        } else if (!isTikvDataDiskSizeValid(dataDisk.sizeGib, maxSize)) {
          nextErrors[
            getTopologyErrorKey(componentType, 'dataDiskSize')
          ] = `DATA disk must be 100-${maxSize} GiB and aligned to 100 GiB, or equal to ${maxSize} GiB`
        }

        if (
          dataDisk &&
          (!isPositiveInteger(dataDisk.iops) ||
            dataDisk.iops < TIKV_DATA_DISK_IOPS_MIN ||
            dataDisk.iops > TIKV_DATA_DISK_IOPS_MAX)
        ) {
          nextErrors[
            getTopologyErrorKey(componentType, 'dataDiskIops')
          ] = `DATA disk IOPS must be ${TIKV_DATA_DISK_IOPS_MIN}-${TIKV_DATA_DISK_IOPS_MAX}`
        }

        if (
          dataDisk &&
          (!isPositiveInteger(dataDisk.throughputMbps) ||
            dataDisk.throughputMbps > DATA_DISK_THROUGHPUT_MBPS_MAX)
        ) {
          nextErrors[
            getTopologyErrorKey(componentType, 'dataDiskThroughput')
          ] = `DATA disk throughput must be a positive integer up to ${DATA_DISK_THROUGHPUT_MBPS_MAX}`
        }
      }

      if (isTiflashComponent(componentType)) {
        const dataDisk = getDataDisk(component)

        if (!dataDisk) {
          nextErrors[getTopologyErrorKey(componentType, 'dataDiskSize')] =
            'DATA disk is required'
        } else if (!isTiflashDataDiskSizeValid(dataDisk.sizeGib)) {
          nextErrors[
            getTopologyErrorKey(componentType, 'dataDiskSize')
          ] = `DATA disk must be ${TIFLASH_DATA_DISK_MIN_GIB}-${TIFLASH_DATA_DISK_MAX_GIB} GiB and aligned to 100 GiB, or equal to ${TIFLASH_DATA_DISK_MAX_GIB} GiB`
        }

        if (
          dataDisk &&
          (!isPositiveInteger(dataDisk.iops) ||
            dataDisk.iops < TIFLASH_DATA_DISK_IOPS_MIN ||
            dataDisk.iops > TIFLASH_DATA_DISK_IOPS_MAX)
        ) {
          nextErrors[
            getTopologyErrorKey(componentType, 'dataDiskIops')
          ] = `DATA disk IOPS must be ${TIFLASH_DATA_DISK_IOPS_MIN}-${TIFLASH_DATA_DISK_IOPS_MAX}`
        }

        if (
          dataDisk &&
          (!isPositiveInteger(dataDisk.throughputMbps) ||
            dataDisk.throughputMbps > DATA_DISK_THROUGHPUT_MBPS_MAX)
        ) {
          nextErrors[
            getTopologyErrorKey(componentType, 'dataDiskThroughput')
          ] = `DATA disk throughput must be a positive integer up to ${DATA_DISK_THROUGHPUT_MBPS_MAX}`
        }
      }
    })

    setTopologyErrors(nextErrors)

    return Object.keys(nextErrors).length === 0
  }

  const validateForm = () => {
    const nextErrors: CalculatorFormErrors = {}

    if (formValues.qps < 1000 || !isStepAligned(formValues.qps, 1000)) {
      nextErrors.qps = 'Peak QPS must be at least 1000 and a multiple of 1000'
    }

    if (
      formValues.customerDataSizeGb < 100 ||
      !isStepAligned(formValues.customerDataSizeGb, 100)
    ) {
      nextErrors.customerDataSizeGb =
        'Customer data size must be at least 100 GiB and a multiple of 100'
    }

    if (!formValues.name.trim()) {
      nextErrors.name = 'Name is required'
    }

    if (!formValues.description.trim()) {
      nextErrors.description = 'Description is required'
    }

    setFormErrors(nextErrors)

    return Object.keys(nextErrors).length === 0
  }

  const handleSave = async () => {
    const nextDefaultCalculation = calculateReservedCapacity(
      normalizeInput(formValues)
    )
    const nextCalculation =
      isEditingTopology && topologyComponents
        ? createCalculationWithComponents(
            nextDefaultCalculation,
            topologyComponents
          )
        : nextDefaultCalculation
    const isFormValid = validateForm()
    const isTopologyValid =
      !isEditingTopology || validateTopology(nextCalculation.components)

    if (!isFormValid || !isTopologyValid) {
      notifier.error(
        'Please fix the calculator inputs and topology before saving'
      )
      return
    }

    await onSave(formValues, nextCalculation)
  }

  return (
    <Stack>
      <Typography variant="headline-md">
        Premium Reserved Capacity Calculator
      </Typography>

      <SimpleGrid cols={{ base: 1, md: 2 }} spacing="md">
        <NumberInput
          label="Peak QPS"
          value={formValues.qps}
          min={1000}
          step={1000}
          decimalScale={0}
          allowDecimal={false}
          disabled={isEditingTopology}
          error={formErrors.qps}
          onChange={(value) =>
            setFormValues((prev) => ({
              ...prev,
              qps: normalizeNumber(value)
            }))
          }
        />

        <Stack gap={4}>
          <Typography variant="label-sm">Workload Pattern</Typography>
          <SegmentedControl
            value={formValues.workloadPattern}
            disabled={isEditingTopology}
            data={[
              { value: 'read', label: 'Read' },
              { value: 'write', label: 'Write' },
              { value: 'mixed', label: 'Mixed' }
            ]}
            onChange={(value) =>
              setFormValues((prev) => ({
                ...prev,
                workloadPattern:
                  value as PremiumReservedCapacityCalculatorFormValues['workloadPattern']
              }))
            }
          />
        </Stack>

        <Select
          label="Latency Requirement"
          value={formValues.latencyRequirement}
          disabled={isEditingTopology}
          data={[
            { label: 'P99 100ms', value: 'p99_100ms' },
            { label: 'P99 300ms', value: 'p99_300ms' }
          ]}
          onChange={(value) => {
            if (!value) {
              return
            }
            setFormValues((prev) => ({
              ...prev,
              latencyRequirement:
                value as PremiumReservedCapacityCalculatorFormValues['latencyRequirement']
            }))
          }}
        />

        <NumberInput
          label="Customer Data Size"
          value={formValues.customerDataSizeGb}
          min={100}
          step={100}
          decimalScale={0}
          allowDecimal={false}
          disabled={isEditingTopology}
          rightAddon="GiB"
          error={formErrors.customerDataSizeGb}
          onChange={(value) =>
            setFormValues((prev) => ({
              ...prev,
              customerDataSizeGb: normalizeNumber(value)
            }))
          }
        />
      </SimpleGrid>

      <Stack gap="sm">
        <Typography variant="title-md">Fixed Sizing Assumptions</Typography>
        <SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
          <AssumptionItem
            label="Compression Factor"
            value={RESERVED_CAPACITY_FIXED_ASSUMPTIONS.compressionFactor}
          />
          <AssumptionItem
            label="Replica"
            value={RESERVED_CAPACITY_FIXED_ASSUMPTIONS.replica}
          />
          <AssumptionItem
            label="Storage Water Level"
            value={RESERVED_CAPACITY_FIXED_ASSUMPTIONS.storageWaterLevel}
          />
        </SimpleGrid>
      </Stack>

      <Divider />

      <Stack gap="sm">
        <Group justify="space-between" align="center">
          <Typography variant="title-md">Sizing Result</Typography>
          <Group gap={8}>
            {isEditingTopology && (
              <Button
                variant="outline"
                leftSection={<IconRefreshCcw01 size={16} />}
                onClick={handleResetTopology}
              >
                Reset
              </Button>
            )}
            {!isEditingTopology && (
              <Button
                variant="outline"
                leftSection={<IconEdit03 size={16} />}
                onClick={handleEditTopology}
              >
                Edit Topology
              </Button>
            )}
          </Group>
        </Group>
        {isEditingTopology && (
          <Box
            p="sm"
            style={{
              background: '#FFFBE6',
              border: '1px solid #FFE58F',
              borderRadius: 8
            }}
          >
            <Typography variant="body-sm">
              Manual topology changes may no longer satisfy the QPS, latency, or
              data size targets from the calculator inputs.
            </Typography>
          </Box>
        )}
        <Box
          p="md"
          style={{
            alignSelf: 'flex-start',
            background: '#F6FAF8',
            border: '1px solid #DCE5E2',
            borderRadius: 8,
            minWidth: 280
          }}
        >
          <Stack gap={4}>
            <Typography variant="label-sm" c="carbon.6">
              Total Purchased vCPU
            </Typography>
            <Group gap={8} align="baseline">
              <Typography variant="headline-lg">
                {calculation.estimatedVcpu}
              </Typography>
              <Typography variant="body-sm" c="carbon.6">
                vCPU
              </Typography>
            </Group>
          </Stack>
        </Box>
      </Stack>

      <ProTable<ReservedCapacityComponentPlan>
        columns={componentColumns}
        data={calculation.components}
        enablePagination={false}
        getRowId={(row) => row.componentType}
        mantineTableHeadCellProps={TOPOLOGY_TABLE_CELL_PROPS}
      />

      <Divider />

      <Stack gap="sm">
        <Typography variant="title-md">Save Template</Typography>
        <Stack gap="md" style={{ maxWidth: 720 }}>
          <TextInput
            label="Name"
            placeholder="Template name"
            value={formValues.name}
            error={formErrors.name}
            onChange={(event) =>
              setFormValues((prev) => ({
                ...prev,
                name: event.currentTarget.value
              }))
            }
          />

          <Textarea
            label="Description"
            placeholder="Describe why this template is being created"
            value={formValues.description}
            error={formErrors.description}
            autosize
            minRows={3}
            onChange={(event) =>
              setFormValues((prev) => ({
                ...prev,
                description: event.currentTarget.value
              }))
            }
          />
        </Stack>
      </Stack>

      <Group justify="flex-end">
        <Button variant="outline" onClick={onCancel}>
          Cancel
        </Button>
        <Button loading={loading} onClick={handleSave}>
          Save Template
        </Button>
      </Group>
    </Stack>
  )
}
