use {
    crate::utils::{
        bitflags::BitflagsExt, clonecell::UnsafeCellCloneSafe, ptr_ext::PtrExt, stack::Stack,
    },
    bstr::{BString, ByteSlice},
    std::{
        fmt::{Debug, Formatter},
        rc::Rc,
    },
    thiserror::Error,
};

#[derive(Copy, Clone, Debug)]
pub enum ColorBitDepth {
    Undefined,
    Bits6,
    Bits8,
    Bits10,
    Bits12,
    Bits14,
    Bits16,
    Reserved,
}

#[derive(Copy, Clone, Debug)]
pub enum DigitalVideoInterfaceStandard {
    Undefined,
    Dvi,
    HdmiA,
    HdmiB,
    MDDI,
    DisplayPort,
    #[expect(dead_code)]
    Unknown(u8),
}

#[derive(Copy, Clone)]
pub struct SignalLevelStandard(u8);

impl Debug for SignalLevelStandard {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let s = match self.0 {
            0 => "+0.7/−0.3 V",
            1 => "+0.714/−0.286 V",
            2 => "+1.0/−0.4 V",
            _ => "+0.7/0 V",
        };
        Debug::fmt(s, f)
    }
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub enum VideoInputDefinition {
    Analog {
        signal_level_standard: SignalLevelStandard,
        blank_to_black_setup_or_pedestal: bool,
        separate_h_v_sync_supported: bool,
        composite_sync_on_horizontal_supported: bool,
        composite_sync_on_green_supported: bool,
        serration_on_vertical_sync_supported: bool,
    },
    Digital {
        bit_depth: ColorBitDepth,
        video_interface: DigitalVideoInterfaceStandard,
    },
}

#[derive(Copy, Clone, Debug)]
pub struct ScreenDimensions {
    pub horizontal_screen_size_cm: Option<u8>,
    pub vertical_screen_size_cm: Option<u8>,
    pub landscape_aspect_ration: Option<f64>,
    pub portrait_aspect_ration: Option<f64>,
}

#[derive(Copy, Clone, Debug)]
pub struct ChromaticityCoordinates {
    pub red_x: u16,
    pub red_y: u16,
    pub green_x: u16,
    pub green_y: u16,
    pub blue_x: u16,
    pub blue_y: u16,
    pub white_x: u16,
    pub white_y: u16,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct EstablishedTimings {
    pub s_720x400_70: bool,
    pub s_720x400_88: bool,
    pub s_640x480_60: bool,
    pub s_640x480_67: bool,
    pub s_640x480_72: bool,
    pub s_640x480_75: bool,
    pub s_800x600_56: bool,
    pub s_800x600_60: bool,
    pub s_800x600_72: bool,
    pub s_800x600_75: bool,
    pub s_832x624_75: bool,
    pub s_1024x768_87: bool,
    pub s_1024x768_60: bool,
    pub s_1024x768_70: bool,
    pub s_1024x768_75: bool,
    pub s_1280x1024_75: bool,
    pub s_1152x870_75: bool,
}

#[derive(Copy, Clone, Debug)]
pub enum AspectRatio {
    A1_1,
    A16_10,
    A4_3,
    A5_4,
    A16_9,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct StandardTiming {
    pub x_resolution: u16,
    pub aspect_ratio: AspectRatio,
    pub vertical_frequency: u8,
}

#[derive(Copy, Clone, Debug)]
pub enum AnalogSyncType {
    AnalogComposite,
    BipolarAnalogComposite,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub enum SyncSignal {
    Analog {
        ty: AnalogSyncType,
        with_serrations: bool,
        sync_on_all_signals: bool,
    },
    DigitalComposite {
        with_serration: bool,
        horizontal_sync_is_positive: bool,
    },
    DigitalSeparate {
        vertical_sync_is_positive: bool,
        horizontal_sync_is_positive: bool,
    },
}

#[derive(Copy, Clone)]
pub enum StereoViewingSupport {
    None,
    FieldSequentialRightDuringStereoSync,
    FieldSequentialLeftDuringStereoSync,
    TwoWayInterleavedRightImageOnEvenLines,
    TwoWayInterleavedLeftImageOnEvenLines,
    FourWayInterleaved,
    SideBySideInterleaved,
}

impl Debug for StereoViewingSupport {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let msg = match *self {
            StereoViewingSupport::None => "none",
            StereoViewingSupport::FieldSequentialRightDuringStereoSync => {
                "field sequential, right during stereo sync"
            }
            StereoViewingSupport::FieldSequentialLeftDuringStereoSync => {
                "field sequential, left during stereo sync"
            }
            StereoViewingSupport::TwoWayInterleavedRightImageOnEvenLines => {
                "2-way interleaved, right image on even lines"
            }
            StereoViewingSupport::TwoWayInterleavedLeftImageOnEvenLines => {
                "2-way interleaved, left image on even lines"
            }
            StereoViewingSupport::FourWayInterleaved => "4-way interleaved",
            StereoViewingSupport::SideBySideInterleaved => "side-by-side interleaved",
        };
        write!(f, "\"{}\"", msg)
    }
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct DisplayRangeLimitsAndAdditionalTiming {
    pub vertical_field_rate_min: u16,
    pub vertical_field_rate_max: u16,
    pub horizontal_field_rate_min: u16,
    pub horizontal_field_rate_max: u16,
    pub maximum_pixel_clock_mhz: u16,
    pub extended_timing_information: ExtendedTimingInformation,
}

#[derive(Copy, Clone, Debug)]
pub enum AspectRatioPreference {
    A4_3,
    A16_9,
    A16_10,
    A5_4,
    A15_9,
    #[expect(dead_code)]
    Unknown(u8),
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub enum ExtendedTimingInformation {
    DefaultGtf,
    NoTimingInformation,
    SecondaryGtf {
        start_frequency: u16,
        c_value: u16,
        m_value: u16,
        k_value: u8,
        j_value: u16,
    },
    Cvt {
        cvt_major_version: u8,
        cvt_minor_version: u8,
        additional_clock_precision: u8,
        maximum_active_pixels_per_line: Option<u16>,
        ar_4_3: bool,
        ar_16_9: bool,
        ar_16_10: bool,
        ar_5_4: bool,
        ar_15_9: bool,
        ar_preference: AspectRatioPreference,
        cvt_rb_reduced_blanking_preferred: bool,
        cvt_standard_blanking: bool,
        scaling_support_horizontal_shrink: bool,
        scaling_support_horizontal_stretch: bool,
        scaling_support_vertical_shrink: bool,
        scaling_support_vertical_stretch: bool,
        preferred_vertical_refresh_rate_hz: u8,
    },
    Unknown(u8),
}

#[derive(Copy, Clone, Debug, Default)]
#[expect(dead_code)]
pub struct ColorPoint {
    pub white_point_index: u8,
    pub white_point_x: u16,
    pub white_point_y: u16,
    pub gamma: Option<f64>,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct EstablishedTimings3 {
    pub s640x350_85: bool,
    pub s640x400_85: bool,
    pub s720x400_85: bool,
    pub s640x480_85: bool,
    pub s848x480_60: bool,
    pub s800x600_85: bool,
    pub s1024x768_85: bool,
    pub s1152x864_75: bool,
    pub s1280x768_60_rb: bool,
    pub s1280x768_60: bool,
    pub s1280x768_75: bool,
    pub s1280x768_85: bool,
    pub s1280x960_60: bool,
    pub s1280x960_85: bool,
    pub s1280x1024_60: bool,
    pub s1280x1024_85: bool,
    pub s1360x768_60: bool,
    pub s1440x900_60_rb: bool,
    pub s1440x900_60: bool,
    pub s1440x900_75: bool,
    pub s1440x900_85: bool,
    pub s1400x1050_60_rb: bool,
    pub s1400x1050_60: bool,
    pub s1400x1050_75: bool,
    pub s1400x1050_85: bool,
    pub s1680x1050_60_rb: bool,
    pub s1680x1050_60: bool,
    pub s1680x1050_75: bool,
    pub s1680x1050_85: bool,
    pub s1600x1200_60: bool,
    pub s1600x1200_65: bool,
    pub s1600x1200_70: bool,
    pub s1600x1200_75: bool,
    pub s1600x1200_85: bool,
    pub s1792x1344_60: bool,
    pub s1792x1344_75: bool,
    pub s1856x1392_60: bool,
    pub s1856x1392_75: bool,
    pub s1920x1200_60_rb: bool,
    pub s1920x1200_60: bool,
    pub s1920x1200_75: bool,
    pub s1920x1200_85: bool,
    pub s1920x1440_60: bool,
    pub s1920x1440_75: bool,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct ColorManagementData {
    pub red_a3: u16,
    pub red_a2: u16,
    pub green_a3: u16,
    pub green_a2: u16,
    pub blue_a3: u16,
    pub blue_a2: u16,
}

#[derive(Copy, Clone, Debug)]
pub enum CvtAspectRatio {
    A4_3,
    A16_9,
    A16_10,
    A15_9,
}

#[derive(Copy, Clone, Debug)]
pub enum CvtPreferredVerticalRate {
    R50,
    R60,
    R75,
    R85,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct Cvt3ByteCode {
    pub addressable_lines_per_field: u16,
    pub aspect_ration: CvtAspectRatio,
    pub preferred_vertical_rate: CvtPreferredVerticalRate,
    pub r50: bool,
    pub r60: bool,
    pub r75: bool,
    pub r85: bool,
    pub r60_reduced_blanking: bool,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct DetailedTimingDescriptor {
    pub pixel_clock_khz: u32,
    pub horizontal_addressable_pixels: u16,
    pub horizontal_blanking_pixels: u16,
    pub vertical_addressable_lines: u16,
    pub vertical_blanking_lines: u16,
    pub horizontal_front_porch_pixels: u16,
    pub horizontal_sync_pulse_pixels: u16,
    pub vertical_front_porch_lines: u8,
    pub vertical_sync_pulse_lines: u8,
    pub horizontal_addressable_mm: u16,
    pub vertical_addressable_mm: u16,
    pub horizontal_left_border_pixels: u8,
    pub vertical_top_border_pixels: u8,
    pub interlaced: bool,
    pub stereo_viewing_support: StereoViewingSupport,
    pub sync: SyncSignal,
}

#[derive(Clone, Debug)]
#[expect(dead_code)]
pub enum Descriptor {
    Unknown(u8),
    DetailedTimingDescriptor(DetailedTimingDescriptor),
    DisplayProductSerialNumber(String),
    AlphanumericDataString(String),
    DisplayProductName(String),
    DisplayRangeLimitsAndAdditionalTiming(DisplayRangeLimitsAndAdditionalTiming),
    EstablishedTimings3(EstablishedTimings3),
    ColorManagementData(ColorManagementData),
    StandardTimingIdentifier([Option<StandardTiming>; 6]),
    ColorPoint(ColorPoint, Option<ColorPoint>),
    Cvt3ByteCode([Cvt3ByteCode; 4]),
}

type EdidContext = (usize, EdidParseContext);

struct EdidParser<'a> {
    data: &'a [u8],
    pos: usize,
    context: Rc<Stack<EdidContext>>,
    saved_ctx: Vec<EdidContext>,
    errors: Vec<(EdidError, Vec<EdidContext>)>,
}

macro_rules! bail {
    ($slf:expr, $err:expr) => {{
        $slf.saved_ctx = $slf.context.to_vec();
        return Err($err);
    }};
}

#[derive(Clone, Debug)]
pub enum EdidParseContext {
    #[expect(dead_code)]
    ReadingBytes(usize),
    BaseBlock,
    Descriptors,
    Descriptor,
    ChromaticityCoordinates,
    EstablishedTimings,
    StandardTimings,
    ScreenDimensions,
    Gamma,
    FeatureSupport,
    Magic,
    Extension,
    IdManufacturerName,
    VideoInputDefinition,
}

unsafe impl UnsafeCellCloneSafe for EdidParseContext {}

struct EdidPushedContext {
    stack: Rc<Stack<(usize, EdidParseContext)>>,
}

impl Drop for EdidPushedContext {
    fn drop(&mut self) {
        self.stack.pop();
    }
}

impl<'a> EdidParser<'a> {
    fn push_ctx(&self, pc: EdidParseContext) -> EdidPushedContext {
        self.context.push((self.pos, pc));
        EdidPushedContext {
            stack: self.context.clone(),
        }
    }

    fn nest(&self, data: &'a [u8]) -> Self {
        Self {
            data,
            pos: 0,
            context: self.context.clone(),
            saved_ctx: vec![],
            errors: vec![],
        }
    }

    fn store_error(&mut self, error: EdidError) {
        self.errors.push((error, self.saved_ctx.clone()));
    }

    fn is_empty(&self) -> bool {
        self.pos >= self.data.len()
    }

    fn read_n<const N: usize>(&mut self) -> Result<&'a [u8; N], EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::ReadingBytes(N));
        if self.data.len() - self.pos < N {
            bail!(self, EdidError::UnexpectedEof);
        }
        let v = unsafe { self.data[self.pos..].as_ptr().cast::<[u8; N]>().deref() };
        self.pos += N;
        Ok(v)
    }

    fn read_var_n(&mut self, n: usize) -> Result<&'a [u8], EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::ReadingBytes(n));
        if self.data.len() - self.pos < n {
            bail!(self, EdidError::UnexpectedEof);
        }
        let v = &self.data[self.pos..self.pos + n];
        self.pos += n;
        Ok(v)
    }

    fn read_u8(&mut self) -> Result<u8, EdidError> {
        let &[a] = self.read_n()?;
        Ok(a)
    }

    fn read_u16(&mut self) -> Result<u16, EdidError> {
        let &[lo, hi] = self.read_n()?;
        Ok(((hi as u16) << 8) + lo as u16)
    }

    fn read_u32(&mut self) -> Result<u32, EdidError> {
        let &[a, b, c, d] = self.read_n()?;
        Ok(((d as u32) << 24) + ((c as u32) << 16) + ((b as u32) << 8) + a as u32)
    }

    fn parse_magic(&mut self) -> Result<(), EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::Magic);
        let magic = self.read_n::<8>()?;
        if magic != &[0, 255, 255, 255, 255, 255, 255, 0] {
            bail!(self, EdidError::InvalidMagic(magic.as_bstr().to_owned()));
        }
        Ok(())
    }

    fn parse_id_manufacturer_name(&mut self) -> Result<BString, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::IdManufacturerName);
        let name = self.read_n::<2>()?;
        let a = (name[0] >> 2) & 0b11111;
        let b = ((name[0] & 0b11) << 3) | (name[1] >> 5);
        let c = name[1] & 0b11111;
        let name = [a + b'@', b + b'@', c + b'@'].as_bstr().to_owned();
        Ok(name)
    }

    fn parse_video_input_definition(&mut self) -> Result<VideoInputDefinition, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::VideoInputDefinition);
        let val = self.read_u8()?;
        let res = if val.contains(0x80) {
            VideoInputDefinition::Digital {
                bit_depth: match (val >> 4) & 0b111 {
                    0b000 => ColorBitDepth::Undefined,
                    0b001 => ColorBitDepth::Bits6,
                    0b010 => ColorBitDepth::Bits8,
                    0b011 => ColorBitDepth::Bits10,
                    0b100 => ColorBitDepth::Bits12,
                    0b101 => ColorBitDepth::Bits14,
                    0b110 => ColorBitDepth::Bits16,
                    _ => ColorBitDepth::Reserved,
                },
                video_interface: match val & 0b1111 {
                    0b0000 => DigitalVideoInterfaceStandard::Undefined,
                    0b0001 => DigitalVideoInterfaceStandard::Dvi,
                    0b0010 => DigitalVideoInterfaceStandard::HdmiA,
                    0b0011 => DigitalVideoInterfaceStandard::HdmiB,
                    0b0100 => DigitalVideoInterfaceStandard::MDDI,
                    0b0101 => DigitalVideoInterfaceStandard::DisplayPort,
                    n => DigitalVideoInterfaceStandard::Unknown(n),
                },
            }
        } else {
            VideoInputDefinition::Analog {
                signal_level_standard: SignalLevelStandard((val >> 5) & 0b11),
                blank_to_black_setup_or_pedestal: (val >> 4).contains(1),
                separate_h_v_sync_supported: (val >> 3).contains(1),
                composite_sync_on_horizontal_supported: (val >> 2).contains(1),
                composite_sync_on_green_supported: (val >> 1).contains(1),
                serration_on_vertical_sync_supported: (val >> 0).contains(1),
            }
        };
        Ok(res)
    }

    fn parse_screen_dimensions(&mut self) -> Result<ScreenDimensions, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::ScreenDimensions);
        let &[hor, vert] = self.read_n()?;
        let mut res = ScreenDimensions {
            horizontal_screen_size_cm: None,
            vertical_screen_size_cm: None,
            landscape_aspect_ration: None,
            portrait_aspect_ration: None,
        };
        if hor != 0 && vert != 0 {
            res.horizontal_screen_size_cm = Some(hor);
            res.vertical_screen_size_cm = Some(vert);
        } else if vert != 0 {
            res.portrait_aspect_ration = Some(100.0 / (vert as f64 + 99.0));
        } else if hor != 0 {
            res.landscape_aspect_ration = Some((hor as f64 + 99.0) / 100.0);
        }
        Ok(res)
    }

    fn parse_gamma(&mut self) -> Result<Option<f64>, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::Gamma);
        let val = self.read_u8()?;
        if val == 0xff {
            Ok(None)
        } else {
            Ok(Some((val as f64 + 100.0) / 100.0))
        }
    }

    fn parse_feature_support(&mut self, digital: bool) -> Result<FeatureSupport, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::FeatureSupport);
        let val = self.read_u8()?;
        Ok(FeatureSupport {
            standby_supported: val.contains(0x80),
            suspend_supported: val.contains(0x40),
            active_off_supported: val.contains(0x20),
            features: if digital {
                FeatureSupport2::Digital {
                    rgb444_supported: true,
                    ycrcb422_supported: val.contains(0x10),
                    ycrcb444_supported: val.contains(0x08),
                }
            } else {
                FeatureSupport2::Analog {
                    display_color_type: match (val >> 3) & 0b11 {
                        0b00 => DisplayColorType::Monochrome,
                        0b01 => DisplayColorType::Rgb,
                        0b10 => DisplayColorType::NonRgb,
                        _ => DisplayColorType::Undefined,
                    },
                }
            },
            srgb_is_default_color_space: val.contains(0x04),
            preferred_mode_is_native: val.contains(0x02),
            display_is_continuous_frequency: val.contains(0x01),
        })
    }

    fn parse_chromaticity_coordinates(&mut self) -> Result<ChromaticityCoordinates, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::ChromaticityCoordinates);
        let b = self.read_n::<10>()?;
        let rx = ((b[0] as u16 >> 6) & 0b11) + ((b[2] as u16) << 2);
        let ry = ((b[0] as u16 >> 4) & 0b11) + ((b[3] as u16) << 2);
        let gx = ((b[0] as u16 >> 2) & 0b11) + ((b[4] as u16) << 2);
        let gy = ((b[0] as u16 >> 0) & 0b11) + ((b[5] as u16) << 2);
        let bx = ((b[1] as u16 >> 6) & 0b11) + ((b[6] as u16) << 2);
        let by = ((b[1] as u16 >> 4) & 0b11) + ((b[7] as u16) << 2);
        let wx = ((b[1] as u16 >> 2) & 0b11) + ((b[8] as u16) << 2);
        let wy = ((b[1] as u16 >> 0) & 0b11) + ((b[9] as u16) << 2);
        Ok(ChromaticityCoordinates {
            red_x: rx,
            red_y: ry,
            green_x: gx,
            green_y: gy,
            blue_x: bx,
            blue_y: by,
            white_x: wx,
            white_y: wy,
        })
    }

    fn parse_established_timings(&mut self) -> Result<EstablishedTimings, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::EstablishedTimings);
        let b = self.read_n::<3>()?;
        Ok(EstablishedTimings {
            s_720x400_70: b[0].contains(0x80),
            s_720x400_88: b[0].contains(0x40),
            s_640x480_60: b[0].contains(0x20),
            s_640x480_67: b[0].contains(0x10),
            s_640x480_72: b[0].contains(0x08),
            s_640x480_75: b[0].contains(0x04),
            s_800x600_56: b[0].contains(0x02),
            s_800x600_60: b[0].contains(0x01),
            s_800x600_72: b[0].contains(0x80),
            s_800x600_75: b[0].contains(0x40),
            s_832x624_75: b[0].contains(0x20),
            s_1024x768_87: b[0].contains(0x10),
            s_1024x768_60: b[0].contains(0x08),
            s_1024x768_70: b[0].contains(0x04),
            s_1024x768_75: b[0].contains(0x02),
            s_1280x1024_75: b[0].contains(0x01),
            s_1152x870_75: b[0].contains(0x80),
        })
    }

    fn parse_standard_timing(&mut self, revision: u8, a: u8, b: u8) -> Option<StandardTiming> {
        if a == 0 {
            return None;
        }
        Some(StandardTiming {
            x_resolution: (a as u16 + 31) * 8,
            aspect_ratio: match b >> 6 {
                0b00 if revision < 3 => AspectRatio::A1_1,
                0b00 => AspectRatio::A16_10,
                0b01 => AspectRatio::A4_3,
                0b10 => AspectRatio::A5_4,
                _ => AspectRatio::A16_9,
            },
            vertical_frequency: 60 + (b & 0b111111),
        })
    }

    fn parse_standard_timings2(
        &mut self,
        revision: u8,
        b: &[u8; 18],
    ) -> [Option<StandardTiming>; 6] {
        let mut res = [None; 6];
        for i in 0..6 {
            let x = b[5 + 2 * i];
            let y = b[5 + 2 * i + 1];
            res[i] = self.parse_standard_timing(revision, x, y);
        }
        res
    }

    fn parse_color_point(&mut self, b: &[u8; 18]) -> (ColorPoint, Option<ColorPoint>) {
        let mut res = [Default::default(); 2];
        for n in 0..2 {
            let b = &b[5 * (n + 1)..];
            res[n] = ColorPoint {
                white_point_index: b[0],
                white_point_x: ((b[2] as u16) << 2) | ((b[1] as u16) >> 2),
                white_point_y: ((b[3] as u16) << 2) | ((b[1] as u16) & 0b11),
                gamma: if b[4] == 0xff {
                    None
                } else {
                    Some((b[5] as f64 + 100.0) / 100.0)
                },
            };
        }
        let second = if res[1].white_point_index != 0 {
            Some(res[1])
        } else {
            None
        };
        (res[0], second)
    }

    fn parse_standard_timings(
        &mut self,
        revision: u8,
    ) -> Result<[Option<StandardTiming>; 8], EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::StandardTimings);
        let bytes = self.read_n::<16>()?;
        let mut res = [None; 8];
        for i in 0..8 {
            let a = bytes[2 * i];
            let b = bytes[2 * i + 1];
            if (a, b) != (1, 1) {
                res[i] = self.parse_standard_timing(revision, a, b);
            }
        }
        Ok(res)
    }

    fn parse_detailed_timing_descriptor(&self, b: &[u8; 18]) -> DetailedTimingDescriptor {
        let l = b[17];
        DetailedTimingDescriptor {
            pixel_clock_khz: u16::from_le_bytes([b[0], b[1]]) as u32 * 10_000,
            horizontal_addressable_pixels: u16::from_le_bytes([b[2], b[4] >> 4]),
            horizontal_blanking_pixels: u16::from_le_bytes([b[3], b[4] & 0b1111]),
            vertical_addressable_lines: u16::from_le_bytes([b[5], b[7] >> 4]),
            vertical_blanking_lines: u16::from_le_bytes([b[6], b[7] & 0b1111]),
            horizontal_front_porch_pixels: u16::from_le_bytes([b[8], b[11] >> 6]),
            horizontal_sync_pulse_pixels: u16::from_le_bytes([b[9], (b[11] >> 4) & 0b11]),
            vertical_front_porch_lines: (b[10] >> 4) | ((b[11] & 0b1100) << 2),
            vertical_sync_pulse_lines: (b[10] & 0b1111) | ((b[11] & 0b11) << 4),
            horizontal_addressable_mm: u16::from_le_bytes([b[12], b[14] >> 4]),
            vertical_addressable_mm: u16::from_le_bytes([b[13], b[14] & 0b1111]),
            horizontal_left_border_pixels: b[15],
            vertical_top_border_pixels: b[16],
            interlaced: l.contains(0x80),
            stereo_viewing_support: match ((l >> 4) & 0b110) | (l & 0b1) {
                0b010 => StereoViewingSupport::FieldSequentialRightDuringStereoSync,
                0b100 => StereoViewingSupport::FieldSequentialLeftDuringStereoSync,
                0b011 => StereoViewingSupport::TwoWayInterleavedRightImageOnEvenLines,
                0b101 => StereoViewingSupport::TwoWayInterleavedLeftImageOnEvenLines,
                0b110 => StereoViewingSupport::FourWayInterleaved,
                0b111 => StereoViewingSupport::SideBySideInterleaved,
                _ => StereoViewingSupport::None,
            },
            sync: if l.contains(0b10000) {
                if l.contains(0b01000) {
                    SyncSignal::DigitalSeparate {
                        vertical_sync_is_positive: l.contains(0b100),
                        horizontal_sync_is_positive: l.contains(0b10),
                    }
                } else {
                    SyncSignal::DigitalComposite {
                        with_serration: l.contains(0b100),
                        horizontal_sync_is_positive: l.contains(0b10),
                    }
                }
            } else {
                SyncSignal::Analog {
                    ty: if l.contains(0b1000) {
                        AnalogSyncType::BipolarAnalogComposite
                    } else {
                        AnalogSyncType::AnalogComposite
                    },
                    with_serrations: l.contains(0b100),
                    sync_on_all_signals: l.contains(0b10),
                }
            },
        }
    }

    fn parse_display_range_limits_and_additional_timing(
        &self,
        b: &[u8; 18],
    ) -> DisplayRangeLimitsAndAdditionalTiming {
        let min_vert_off = b[4].contains(0b0001);
        let max_vert_off = min_vert_off || b[4].contains(0b0010);
        let min_horz_off = b[4].contains(0b0100);
        let max_horz_off = min_horz_off || b[4].contains(0b1000);
        DisplayRangeLimitsAndAdditionalTiming {
            vertical_field_rate_min: b[5] as u16 + if min_vert_off { 255 } else { 0 },
            vertical_field_rate_max: b[6] as u16 + if max_vert_off { 255 } else { 0 },
            horizontal_field_rate_min: b[7] as u16 + if min_horz_off { 255 } else { 0 },
            horizontal_field_rate_max: b[8] as u16 + if max_horz_off { 255 } else { 0 },
            maximum_pixel_clock_mhz: b[9] as u16 * 10,
            extended_timing_information: match b[10] {
                0x0 => ExtendedTimingInformation::DefaultGtf,
                0x1 => ExtendedTimingInformation::NoTimingInformation,
                0x2 => ExtendedTimingInformation::SecondaryGtf {
                    start_frequency: b[12] as u16,
                    c_value: b[13] as u16,
                    m_value: u16::from_le_bytes([b[14], b[15]]),
                    k_value: b[16],
                    j_value: b[17] as u16,
                },
                0x4 => ExtendedTimingInformation::Cvt {
                    cvt_major_version: b[11] >> 4,
                    cvt_minor_version: b[11] & 0b1111,
                    additional_clock_precision: b[12] >> 2,
                    maximum_active_pixels_per_line: if b[13] == 0 {
                        None
                    } else {
                        Some((((b[12] as u16 & 0b11) << 8) | b[13] as u16) * 8)
                    },
                    ar_4_3: b[14].contains(0x80),
                    ar_16_9: b[14].contains(0x40),
                    ar_16_10: b[14].contains(0x20),
                    ar_5_4: b[14].contains(0x10),
                    ar_15_9: b[14].contains(0x08),
                    ar_preference: match b[15] >> 5 {
                        0b000 => AspectRatioPreference::A4_3,
                        0b001 => AspectRatioPreference::A16_9,
                        0b010 => AspectRatioPreference::A16_10,
                        0b011 => AspectRatioPreference::A5_4,
                        0b100 => AspectRatioPreference::A15_9,
                        n => AspectRatioPreference::Unknown(n),
                    },
                    cvt_rb_reduced_blanking_preferred: b[15].contains(0b10000),
                    cvt_standard_blanking: b[15].contains(0b1000),
                    scaling_support_horizontal_shrink: b[16].contains(0x80),
                    scaling_support_horizontal_stretch: b[16].contains(0x40),
                    scaling_support_vertical_shrink: b[16].contains(0x20),
                    scaling_support_vertical_stretch: b[16].contains(0x10),
                    preferred_vertical_refresh_rate_hz: b[17],
                },
                n => ExtendedTimingInformation::Unknown(n),
            },
        }
    }

    fn parse_established_timings3(&self, b: &[u8; 18]) -> EstablishedTimings3 {
        EstablishedTimings3 {
            s640x350_85: b[6].contains(0x80),
            s640x400_85: b[6].contains(0x40),
            s720x400_85: b[6].contains(0x20),
            s640x480_85: b[6].contains(0x10),
            s848x480_60: b[6].contains(0x08),
            s800x600_85: b[6].contains(0x04),
            s1024x768_85: b[6].contains(0x02),
            s1152x864_75: b[6].contains(0x01),
            s1280x768_60_rb: b[7].contains(0x80),
            s1280x768_60: b[7].contains(0x40),
            s1280x768_75: b[7].contains(0x20),
            s1280x768_85: b[7].contains(0x10),
            s1280x960_60: b[7].contains(0x08),
            s1280x960_85: b[7].contains(0x04),
            s1280x1024_60: b[7].contains(0x02),
            s1280x1024_85: b[7].contains(0x01),
            s1360x768_60: b[8].contains(0x80),
            s1440x900_60_rb: b[8].contains(0x40),
            s1440x900_60: b[8].contains(0x20),
            s1440x900_75: b[8].contains(0x10),
            s1440x900_85: b[8].contains(0x08),
            s1400x1050_60_rb: b[8].contains(0x04),
            s1400x1050_60: b[8].contains(0x02),
            s1400x1050_75: b[8].contains(0x01),
            s1400x1050_85: b[9].contains(0x80),
            s1680x1050_60_rb: b[9].contains(0x40),
            s1680x1050_60: b[9].contains(0x20),
            s1680x1050_75: b[9].contains(0x10),
            s1680x1050_85: b[9].contains(0x08),
            s1600x1200_60: b[9].contains(0x04),
            s1600x1200_65: b[9].contains(0x02),
            s1600x1200_70: b[9].contains(0x01),
            s1600x1200_75: b[10].contains(0x80),
            s1600x1200_85: b[10].contains(0x40),
            s1792x1344_60: b[10].contains(0x20),
            s1792x1344_75: b[10].contains(0x10),
            s1856x1392_60: b[10].contains(0x08),
            s1856x1392_75: b[10].contains(0x04),
            s1920x1200_60_rb: b[10].contains(0x02),
            s1920x1200_60: b[10].contains(0x01),
            s1920x1200_75: b[11].contains(0x80),
            s1920x1200_85: b[11].contains(0x40),
            s1920x1440_60: b[11].contains(0x20),
            s1920x1440_75: b[11].contains(0x10),
        }
    }

    fn parse_color_management_data(&self, b: &[u8; 18]) -> ColorManagementData {
        ColorManagementData {
            red_a3: u16::from_le_bytes([b[6], b[7]]),
            red_a2: u16::from_le_bytes([b[8], b[9]]),
            green_a3: u16::from_le_bytes([b[10], b[11]]),
            green_a2: u16::from_le_bytes([b[12], b[13]]),
            blue_a3: u16::from_le_bytes([b[14], b[15]]),
            blue_a2: u16::from_le_bytes([b[16], b[17]]),
        }
    }

    fn parse_cvt3_byte_codes(&self, b: &[u8; 18]) -> [Cvt3ByteCode; 4] {
        let parse = |n: usize| {
            let b = &b[6 + 3 * n..];
            Cvt3ByteCode {
                addressable_lines_per_field: u16::from_le_bytes([b[0], b[1] >> 4]),
                aspect_ration: match (b[1] >> 2) & 0b11 {
                    0 => CvtAspectRatio::A4_3,
                    1 => CvtAspectRatio::A16_9,
                    2 => CvtAspectRatio::A16_10,
                    _ => CvtAspectRatio::A15_9,
                },
                preferred_vertical_rate: match (b[2] >> 5) & 0b11 {
                    0 => CvtPreferredVerticalRate::R50,
                    1 => CvtPreferredVerticalRate::R60,
                    2 => CvtPreferredVerticalRate::R75,
                    _ => CvtPreferredVerticalRate::R85,
                },
                r50: b[2].contains(0b10000),
                r60: b[2].contains(0b01000),
                r75: b[2].contains(0b00100),
                r85: b[2].contains(0b00010),
                r60_reduced_blanking: b[2].contains(0b00001),
            }
        };
        [parse(0), parse(1), parse(2), parse(3)]
    }

    fn parse_descriptor(&mut self, revision: u8) -> Result<Option<Descriptor>, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::Descriptor);
        let b = self.read_n::<18>()?;
        let str = || {
            let mut s = &b[5..];
            if let Some(n) = s.find_byte(b'\n') {
                s = &s[..n];
            };
            let mut res = String::new();
            for &b in s {
                res.push_str(CP437[b as usize]);
            }
            res
        };
        let res = if (b[0], b[1]) == (0, 0) {
            match b[3] {
                0xff => Descriptor::DisplayProductSerialNumber(str()),
                0xfe => Descriptor::AlphanumericDataString(str()),
                0xfd => Descriptor::DisplayRangeLimitsAndAdditionalTiming(
                    self.parse_display_range_limits_and_additional_timing(b),
                ),
                0xfc => Descriptor::DisplayProductName(str()),
                0xfb => {
                    let (first, second) = self.parse_color_point(b);
                    Descriptor::ColorPoint(first, second)
                }
                0xfa => {
                    Descriptor::StandardTimingIdentifier(self.parse_standard_timings2(revision, b))
                }
                0xf9 => Descriptor::ColorManagementData(self.parse_color_management_data(b)),
                0xf8 => Descriptor::Cvt3ByteCode(self.parse_cvt3_byte_codes(b)),
                0xf7 => Descriptor::EstablishedTimings3(self.parse_established_timings3(b)),
                0x10 => return Ok(None),
                n => Descriptor::Unknown(n),
            }
        } else {
            Descriptor::DetailedTimingDescriptor(self.parse_detailed_timing_descriptor(b))
        };
        Ok(Some(res))
    }

    fn parse_descriptors(&mut self, revision: u8) -> Result<[Option<Descriptor>; 4], EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::Descriptors);
        let mut res = [None, None, None, None];
        for res in &mut res {
            *res = self.parse_descriptor(revision)?;
        }
        Ok(res)
    }

    fn parse_base_block(&mut self) -> Result<EdidBaseBlock, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::BaseBlock);
        self.parse_magic()?;
        let id_manufacturer_name = self.parse_id_manufacturer_name()?;
        let id_product_code = self.read_u16()?;
        let id_serial_number = self.read_u32()?;
        let mut week_of_manufacture = None;
        let mut model_year = None;
        let mut year_of_manufacture = None;
        {
            let &[a, b] = self.read_n()?;
            if matches!(a, 1..=0x36) {
                week_of_manufacture = Some(a);
            }
            let year = b as u16 + 1990;
            if a == 0xff {
                model_year = Some(year);
            } else {
                year_of_manufacture = Some(year);
            }
        }
        let &[edid_version, edid_revision] = self.read_n()?;
        let video_input_definition = self.parse_video_input_definition()?;
        let is_digital = matches!(video_input_definition, VideoInputDefinition::Digital { .. });
        let screen_dimensions = self.parse_screen_dimensions()?;
        let gamma = self.parse_gamma()?;
        let feature_support = self.parse_feature_support(is_digital)?;
        let chromaticity_coordinates = self.parse_chromaticity_coordinates()?;
        let established_timings = self.parse_established_timings()?;
        let standard_timings = self.parse_standard_timings(edid_revision)?;
        let descriptors = self.parse_descriptors(edid_revision)?;
        let num_extensions = self.read_u8()?;
        let _checksum = self.read_u8()?;
        Ok(EdidBaseBlock {
            id_manufacturer_name,
            id_product_code,
            id_serial_number,
            week_of_manufacture,
            model_year,
            year_of_manufacture,
            edid_version,
            edid_revision,
            video_input_definition,
            screen_dimensions,
            gamma,
            feature_support,
            chromaticity_coordinates,
            established_timings,
            standard_timings,
            descriptors,
            num_extensions,
        })
    }

    fn parse_cta_amd_vendor_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
        let _ = self.read_n::<2>()?;
        Ok(CtaDataBlock::VendorAmd(CtaAmdVendorDataBlock {
            minimum_refresh_hz: self.read_u8()?,
            maximum_refresh_hz: self.read_u8()?,
        }))
    }

    fn parse_cta_vendor_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
        match self.read_n::<3>()? {
            [0x1A, 0x00, 0x00] => self.parse_cta_amd_vendor_data_block(),
            _ => Ok(CtaDataBlock::Unknown),
        }
    }

    fn parse_cta_colorimetry_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
        let [lo, hi] = *self.read_n::<2>()?;
        Ok(CtaDataBlock::Colorimetry(CtaColorimetryDataBlock {
            bt2020_rgb: lo.contains(0x80),
            bt2020_ycc: lo.contains(0x40),
            bt2020_cycc: lo.contains(0x20),
            op_rgb: lo.contains(0x10),
            op_ycc_601601: lo.contains(0x08),
            s_ycc_601: lo.contains(0x04),
            xv_ycc_709: lo.contains(0x02),
            xv_ycc_601: lo.contains(0x01),
            dci_p3: hi.contains(0x80),
        }))
    }

    fn parse_cta_hdr_static_metadata_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
        let et = self.read_u8()?;
        let _ = self.read_u8()?;
        let mut read_luminance = |min: bool| {
            let v = self.read_u8().unwrap_or_default();
            if v == 0 {
                None
            } else if min {
                Some((v as f64 / 255.0).powi(2) / 100.0)
            } else {
                Some(50.0 * 2.0f64.powf(v as f64 / 32.0))
            }
        };
        Ok(CtaDataBlock::StaticHdrMetadata(
            CtaStaticHdrMetadataDataBlock {
                traditional_gamma_sdr_luminance: et.contains(0x01),
                traditional_gamma_hdr_luminance: et.contains(0x02),
                smpte_st_2084: et.contains(0x04),
                hlg: et.contains(0x08),
                max_luminance: read_luminance(false),
                max_frame_average_luminance: read_luminance(false),
                min_luminance: read_luminance(true),
            },
        ))
    }

    fn parse_cta_extended_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
        match self.read_u8()? {
            0x5 => self.parse_cta_colorimetry_data_block(),
            0x6 => self.parse_cta_hdr_static_metadata_data_block(),
            _ => Ok(CtaDataBlock::Unknown),
        }
    }

    fn parse_cta_data_block(&mut self, tag: u8) -> Result<CtaDataBlock, EdidError> {
        match tag {
            0x3 => self.parse_cta_vendor_data_block(),
            0x7 => self.parse_cta_extended_data_block(),
            _ => Ok(CtaDataBlock::Unknown),
        }
    }

    fn parse_cta_extension_v3(&mut self) -> Result<EdidExtension, EdidError> {
        let detailed_timing_descriptors_offset = self.read_u8()? as usize;
        let _ = self.read_u8()?;
        let mut data_blocks = vec![];
        while self.pos < detailed_timing_descriptors_offset {
            let b1 = self.read_u8()?;
            let data = self.read_var_n(b1 as usize & 0x1f)?;
            let mut parser = self.nest(data);
            match parser.parse_cta_data_block(b1 >> 5) {
                Ok(d) => data_blocks.push(d),
                Err(e) => {
                    self.saved_ctx = parser.saved_ctx;
                    self.store_error(e);
                }
            }
        }
        Ok(EdidExtension::CtaV3(CtaExtensionV3 { data_blocks }))
    }

    fn parse_cta_extension(&mut self) -> Result<EdidExtension, EdidError> {
        // https://web.archive.org/web/20171201033424/https://standards.cta.tech/kwspub/published_docs/CTA-861-G_FINAL_revised_2017.pdf
        match self.read_u8()? {
            0x3 => self.parse_cta_extension_v3(),
            _ => Ok(EdidExtension::Unknown),
        }
    }

    fn parse_extension_impl(&mut self) -> Result<EdidExtension, EdidError> {
        match self.read_u8()? {
            0x2 => self.parse_cta_extension(),
            _ => Ok(EdidExtension::Unknown),
        }
    }

    fn parse_extension(&mut self) -> Result<EdidExtension, EdidError> {
        let _ctx = self.push_ctx(EdidParseContext::Extension);
        let data = self.read_n::<128>()?;
        let mut parser = self.nest(data);
        let res = parser.parse_extension_impl();
        if res.is_err() {
            self.saved_ctx = parser.saved_ctx;
        }
        res
    }

    fn parse(&mut self) -> Result<EdidFile, EdidError> {
        let bb = self.parse_base_block()?;
        let mut exts = vec![];
        while !self.is_empty() {
            match self.parse_extension() {
                Ok(e) => exts.push(e),
                Err(e) => self.store_error(e),
            }
        }
        Ok(EdidFile {
            base_block: bb,
            extension_blocks: exts,
        })
    }
}

#[derive(Debug)]
pub enum DisplayColorType {
    Monochrome,
    Rgb,
    NonRgb,
    Undefined,
}

#[derive(Debug)]
#[expect(dead_code)]
pub enum FeatureSupport2 {
    Analog {
        display_color_type: DisplayColorType,
    },
    Digital {
        rgb444_supported: bool,
        ycrcb444_supported: bool,
        ycrcb422_supported: bool,
    },
}

#[derive(Debug)]
#[expect(dead_code)]
pub struct FeatureSupport {
    pub standby_supported: bool,
    pub suspend_supported: bool,
    pub active_off_supported: bool,
    pub features: FeatureSupport2,
    pub srgb_is_default_color_space: bool,
    pub preferred_mode_is_native: bool,
    pub display_is_continuous_frequency: bool,
}

#[derive(Debug)]
#[expect(dead_code)]
pub struct EdidBaseBlock {
    pub id_manufacturer_name: BString,
    pub id_product_code: u16,
    pub id_serial_number: u32,
    pub week_of_manufacture: Option<u8>,
    pub model_year: Option<u16>,
    pub year_of_manufacture: Option<u16>,
    pub edid_version: u8,
    pub edid_revision: u8,
    pub video_input_definition: VideoInputDefinition,
    pub screen_dimensions: ScreenDimensions,
    pub gamma: Option<f64>,
    pub feature_support: FeatureSupport,
    pub chromaticity_coordinates: ChromaticityCoordinates,
    pub established_timings: EstablishedTimings,
    pub standard_timings: [Option<StandardTiming>; 8],
    pub descriptors: [Option<Descriptor>; 4],
    pub num_extensions: u8,
}

#[derive(Debug)]
pub enum EdidExtension {
    Unknown,
    CtaV3(CtaExtensionV3),
}

#[derive(Debug)]
pub struct CtaExtensionV3 {
    pub data_blocks: Vec<CtaDataBlock>,
}

#[derive(Debug)]
pub enum CtaDataBlock {
    Unknown,
    VendorAmd(CtaAmdVendorDataBlock),
    Colorimetry(CtaColorimetryDataBlock),
    StaticHdrMetadata(CtaStaticHdrMetadataDataBlock),
}

#[derive(Debug)]
pub struct CtaAmdVendorDataBlock {
    pub minimum_refresh_hz: u8,
    #[expect(dead_code)]
    pub maximum_refresh_hz: u8,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct CtaColorimetryDataBlock {
    pub bt2020_rgb: bool,
    pub bt2020_ycc: bool,
    pub bt2020_cycc: bool,
    pub op_rgb: bool,
    pub op_ycc_601601: bool,
    pub s_ycc_601: bool,
    pub xv_ycc_709: bool,
    pub xv_ycc_601: bool,
    pub dci_p3: bool,
}

#[derive(Copy, Clone, Debug)]
#[expect(dead_code)]
pub struct CtaStaticHdrMetadataDataBlock {
    pub traditional_gamma_sdr_luminance: bool,
    pub traditional_gamma_hdr_luminance: bool,
    pub smpte_st_2084: bool,
    pub hlg: bool,
    pub max_luminance: Option<f64>,
    pub max_frame_average_luminance: Option<f64>,
    pub min_luminance: Option<f64>,
}

#[derive(Debug)]
pub struct EdidFile {
    pub base_block: EdidBaseBlock,
    pub extension_blocks: Vec<EdidExtension>,
}

#[derive(Debug, Error)]
pub enum EdidError {
    #[error("Unexpected end-of-file")]
    UnexpectedEof,
    #[error("Invalid magic header")]
    InvalidMagic(BString),
}

pub fn parse(data: &[u8]) -> Result<EdidFile, EdidError> {
    let mut parser = EdidParser {
        data,
        pos: 0,
        context: Rc::new(Default::default()),
        saved_ctx: vec![],
        errors: vec![],
    };
    parser.parse()
}

const CP437: &[&str] = &[
    "\u{0}", "☺", "☻", "♥", "♦", "♣", "♠", "•", "◘", "○", "◙", "♂", "♀", "♪", "♫", "☼", "►", "◄",
    "↕", "‼", "¶", "§", "▬", "↨", "↑", "↓", "→", "←", "∟", "↔", "▲", "▼", " ", "!", "\"", "#", "$",
    "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7",
    "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
    "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]",
    "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
    "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "⌂", "Ç", "ü", "é", "â",
    "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å", "É", "æ", "Æ", "ô", "ö", "ò", "û",
    "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "₧", "ƒ", "á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", "⌐",
    "¬", "½", "¼", "¡", "«", "»", "░", "▒", "▓", "│", "┤", "╡", "╢", "╖", "╕", "╣", "║", "╗", "╝",
    "╜", "╛", "┐", "└", "┴", "┬", "├", "─", "┼", "╞", "╟", "╚", "╔", "╩", "╦", "╠", "═", "╬", "╧",
    "╨", "╤", "╥", "╙", "╘", "╒", "╓", "╫", "╪", "┘", "┌", "█", "▄", "▌", "▐", "▀", "α", "ß", "Γ",
    "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "∞", "φ", "ε", "∩", "≡", "±", "≥", "≤", "⌠", "⌡",
    "÷", "≈", "°", "∙", "·", "√", "ⁿ", "²", "■", "\u{a0}",
];
