import type {CSSProperties} from 'react';
import {Fragment} from 'react';
import styled from '@emotion/styled';

import LetterSpacingGraphic from 'sentry-images/stories/typography/letter-spacing.svg';
import LineHeightGraphic from 'sentry-images/stories/typography/line-height.svg';
import WeightGraphic from 'sentry-images/stories/typography/weight.svg';

import {CodeSnippet} from 'sentry/components/codeSnippet';
import {Flex} from 'sentry/components/container/flex';
import ExternalLink from 'sentry/components/links/externalLink';
import Link from 'sentry/components/links/link';
import Panel from 'sentry/components/panels/panel';
import {PanelTable} from 'sentry/components/panels/panelTable';
import {IconCheckmark, IconCircleFill, IconClose} from 'sentry/icons';
import {space} from 'sentry/styles/space';
import type {ColorOrAlias} from 'sentry/utils/theme';

const FixedWidth = styled('div')`
  max-width: 800px;
`;

interface TypeScaleItem {
  fontSize: CSSProperties['fontSize'];
  fontWeight: CSSProperties['fontWeight'];
  letterSpacing: CSSProperties['letterSpacing'];
  lineHeight: CSSProperties['lineHeight'];
  name: string;
}
const TYPE_SCALE: TypeScaleItem[] = [
  {
    name: 'Heading 1',
    fontWeight: 600,
    fontSize: '2.25rem',
    lineHeight: 1.2,
    letterSpacing: '-0.02rem',
  },
  {
    name: 'Heading 2',
    fontWeight: 600,
    fontSize: '1.875rem',
    lineHeight: 1.2,
    letterSpacing: '-0.016em',
  },
  {
    name: 'Heading 3',
    fontWeight: 600,
    fontSize: '1.625rem',
    lineHeight: 1.2,
    letterSpacing: '-0.012em',
  },
  {
    name: 'Heading 4',
    fontWeight: 600,
    fontSize: '1.375rem',
    lineHeight: 1.2,
    letterSpacing: '-0.008em',
  },
  {
    name: 'Heading 5',
    fontWeight: 600,
    fontSize: '1.25rem',
    lineHeight: 1.2,
    letterSpacing: '-0.004em',
  },
  {
    name: 'Heading 6',
    fontWeight: 600,
    fontSize: '1.125rem',
    lineHeight: 1.2,
    letterSpacing: 'normal',
  },
  {
    name: 'Paragraph',
    fontWeight: 400,
    fontSize: '1rem',
    lineHeight: 1.4,
    letterSpacing: 'normal',
  },
  {
    name: 'Button/Label',
    fontWeight: 600,
    fontSize: '1rem',
    lineHeight: 1.2,
    letterSpacing: 'normal',
  },
  {
    name: 'Small',
    fontWeight: 400,
    fontSize: '0.875rem',
    lineHeight: 1.4,
    letterSpacing: '+0.01rem',
  },
];

const InlineLinkExampleStyles = `styled('a')\`
  color: \${p => p.theme.blue300};
  text-decoration: underline;
  text-decoration-color: ${p => p.theme.blue100};
  cursor: pointer;

  &:hover {
    text-decoration-color: ${p => p.theme.blue200};
  }
\`;
`;

const StandaloneLinkExampleStyles = `/* Link color is flexible, choose between Gray 500, 400, and 300. */
styled('a')\`
  color: \${p => p.theme.gray500};
  text-decoration: none;
  cursor: pointer;

  &:hover {
    text-decoration: underline;
    text-decoration-color: \${p => p.theme.gray200};
  }
\`;
`;

const UnorderedListExampleStyles = `/* First-level items */
ul > li {
  list-style-type: disc;
}

/* Second-level items */
ul > ul > li {
  list-style-type: circle;
}
`;

const OrderedListExampleStyles = `/* First-level items */
ul > li {
  list-style-type: decimal;
}

/* Second-level items */
ul > ul > li {
  list-style-type: lower-alpha;
}
`;

const TabularNumsExampleStyles = `/* Add this to numeric columns */
font-variant-numeric: tabular-nums;
`;

const FontLigatureExampleStyles = `/* Add this to the root element */
font-feature-settings: 'liga';
`;

const FontFractionExampleStyles = `/* Be careful: this changes the appearance of normal,
non-fractional numbers, so only apply it to specific
text elements with fractions inside. */
font-feature-settings: 'frac';
`;

export default function TypographyStories() {
  return (
    <FixedWidth>
      <h3>Typography</h3>
      <p>
        We've built Sentry's type system around Rubik - a playful open-source typeface.
        For code and code-like elements, we use <code>Roboto Mono</code>.
      </p>
      <hr />
      <h4>Type scale</h4>
      <p>
        Type scales are hierarchical type systems consisting of style definitions for
        common elements, such as Heading 1, Heading 2, Paragraph, and Button/Label.
      </p>
      <p>
        Sentry's type scale is based on the Rubik typeface. The root font size is 16px
        (1rem = 16px).
      </p>
      <PanelTable headers={['Scale', 'Weight', 'Size', 'Line Height', 'Letter Spacing']}>
        {TYPE_SCALE.map(({name, ...props}) => {
          return (
            <Fragment key={name}>
              <div style={props}>{name}</div>
              <div>{props.fontWeight}</div>
              <div>{props.fontSize}</div>
              <div>{props.lineHeight}</div>
              <div>{props.letterSpacing}</div>
            </Fragment>
          );
        })}
      </PanelTable>
      <hr />
      <h4>Styling</h4>
      <p>
        The type scale above should cover a large majority of use cases. However, if an
        element requires a custom style outside of the type scale, make sure to follow the
        rules below.
      </p>
      <h5>Size</h5>
      <p>
        <strong>Use values from the type scale above.</strong> Be mindful of the type
        hierarchy. If the element has low importance, use a smaller size.
      </p>
      <p>
        <Flex gap={space(1)} align="flex-start">
          <PositiveLabel />
          Always define font sizes with the <code>rem</code> unit.
        </Flex>
      </p>
      <h5>Weight</h5>
      <p>
        <ExampleImg src={WeightGraphic} />
      </p>
      <ul>
        <li>
          <strong>Use Medium (600)</strong> for headings, button labels, and elements that
          need to stand out from the rest of the user interface, like table headers
        </li>
        <li>
          <strong>Use Regular (400)</strong> for all other elements
        </li>
      </ul>
      <h5>Line height</h5>
      <p>
        <ExampleImg src={LineHeightGraphic} />
      </p>
      <ul>
        <li>
          <strong>Use 1.4</strong> for body text (content that can wrap to multiple lines)
        </li>
        <li>
          <strong>Use 1.2</strong> for headings and short, single-line text like table
          headers and input fields
        </li>
        <li>
          <strong>Use 1</strong> for text labels with immediate bounding boxes, like
          buttons, pills, and badges
        </li>
      </ul>
      <h5>Letter spacing</h5>
      <p>
        <ExampleImg src={LetterSpacingGraphic} />
      </p>
      <ul>
        <li>
          <strong>Reduce letter spacing for headings.</strong> This makes them look more
          condensed, thereby reinforcing their high order in the type hierarchy. Refer to
          the type scale above for how much to reduce.
        </li>
        <li>
          <strong>
            Increase letter spacing (+0.02rem) in text elements that are smaller than
            16px,
          </strong>
          with the exception of code and code-like elements. This makes them easier to
          read.
        </li>
      </ul>
      <hr />
      <h4>Code</h4>
      <p>
        Use Roboto Mono in Regular (400) for code and code-like elements, like search
        tokens.
      </p>
      <p>Set the line height based on the context:</p>
      <ul>
        <li>
          <strong>For multi-line code</strong>, use 1.6
        </li>
        <li>
          <strong>For single-line code elements</strong>, like search tokens, use the same
          line height as that of the text surrounding the token
        </li>
      </ul>
      <hr />
      <h4>External Links</h4>
      <p>
        External links lead users to pages outside the application. Examples include links
        to Sentry's blog/marketing pages, terms of service, third-party documentation,…
      </p>
      <p>
        The following styling rules apply to external links only. Internal links, on the
        other hand, can have more flexible styles, based on their behavior and context.
      </p>
      <h5>In a sentence</h5>
      <p>When a link appears inside a longer sentence…</p>
      <ExamplePanel>
        ... like this{' '}
        <FixedExternalLink onClick={() => {}}>little link</FixedExternalLink>.
      </ExamplePanel>
      <ul>
        <li>
          Use <ColorSwatch color="blue300" /> as the text color
        </li>
        <li>
          Add a solid underline in <ColorSwatch color="blue100" />
        </li>
        <li>
          Don't include any preceding articles (a, the, this, our) in the linked text, for
          example:
          <ul>
            <li>
              <Flex gap={space(1)} align="baseline">
                <PositiveLabel style={{alignSelf: 'flex-end'}} /> the{' '}
                <FixedExternalLink onClick={() => {}}>
                  Church of the Flying Spaghetti Monster
                </FixedExternalLink>
              </Flex>
            </li>
            <li>
              <Flex gap={space(1)} align="baseline">
                <NegativeLabel style={{alignSelf: 'flex-end'}} />{' '}
                <FixedExternalLink onClick={() => {}}>
                  the Church of the Flying Spaghetti Monster
                </FixedExternalLink>
              </Flex>
            </li>
          </ul>
        </li>
        <li>
          On hover:
          <ul>
            <li>
              Use a pointer cursor - <code>cursor: pointer</code>
            </li>
            <li>
              Change the underline color to <ColorSwatch color="blue200" />
            </li>
          </ul>
        </li>
      </ul>
      <p>
        <CodeSnippet filename="Styled Components" language="typescript">
          {InlineLinkExampleStyles}
        </CodeSnippet>
      </p>
      <h5>Standalone</h5>
      <p>
        When a link appears on its own and the user likely knows that it's a link given
        the context, like in a footer:
      </p>
      <ExamplePanel>
        <Flex column>
          <FooterLink to="">Privacy Policy</FooterLink>
          <FooterLink to="">Terms of Use</FooterLink>
        </Flex>
      </ExamplePanel>
      <ul>
        <li>
          Use <ColorSwatch color="gray500" />, <ColorSwatch color="gray400" />, or{' '}
          <ColorSwatch color="gray300" />, depending on the context
        </li>
        <li>Don't add any underline</li>
        <li>
          On hover:
          <ul>
            <li>
              Use a pointer cursor - <code>cursor: pointer</code>
            </li>
            <li>
              Add a solid underline in <ColorSwatch color="gray200" />
            </li>
          </ul>
        </li>
      </ul>
      <p>
        <CodeSnippet filename="Styled Components" language="typescript">
          {StandaloneLinkExampleStyles}
        </CodeSnippet>
      </p>
      <hr />
      <h4>Lists</h4>
      <h3>Unordered</h3>
      <p>Use filled and hollow circles as bullets points:</p>
      <ExamplePanel>
        <ul>
          <li>
            Camelus
            <ul>
              <li>Bactrian camel</li>
              <li>Dromedary</li>
            </ul>
          </li>
          <li>
            Lama
            <ul>
              <li>Llama</li>
              <li>Alpaca</li>
            </ul>
          </li>
        </ul>
      </ExamplePanel>
      <p>
        <CodeSnippet filename="CSS" language="css">
          {UnorderedListExampleStyles}
        </CodeSnippet>
      </p>
      <p>
        <Flex gap={space(1)} align="flex-start">
          <PositiveLabel />
          Don't add full stops (.) to the end of each item, unless the item contains
          multiple sentences.
        </Flex>
      </p>
      <p>
        <Flex gap={space(1)} align="flex-start">
          <PositiveLabel /> Avoid using custom symbols and icons as bullet characters, as
          they usually look out of place and distract from the main text content.
        </Flex>
      </p>
      <h5>Ordered</h5>
      <p>Use Arabic numerals and lowercase letters as counters:</p>
      <ExamplePanel>
        <ol>
          <li>
            Camelus
            <ol>
              <li>Bactrian camel</li>
              <li>Dromedary</li>
            </ol>
          </li>
          <li>
            Lama
            <ol>
              <li>Llama</li>
              <li>Alpaca</li>
            </ol>
          </li>
        </ol>
      </ExamplePanel>
      <p>
        <CodeSnippet filename="CSS" language="css">
          {OrderedListExampleStyles}
        </CodeSnippet>
      </p>
      <p>
        <Flex gap={space(1)} align="flex-start">
          <PositiveLabel />
          Avoid using custom symbols and icons as counters.
        </Flex>
      </p>
      <hr />
      <h4>OpenType features</h4>
      <p>
        Rubik supports a few useful{' '}
        <ExternalLink href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/OpenType_fonts_guide">
          OpenType font features
        </ExternalLink>
        . These features, or variants, are alternative characters that, when used in the
        right places, can help improve the reading experience.
      </p>
      <h5>Tabular figures</h5>
      <p>
        By default, Rubik uses proportional figures. This works well in most cases.
        However, for large tables with a lot of numbers, tabular figures would be a better
        choice, thanks to their consistent width and more legible design.
      </p>
      <PanelTable headers={['Proportional Figures', 'Tabular Figures']}>
        <div>999,999</div>
        <TabularNum>999,999</TabularNum>
        <div>111,111</div>
        <TabularNum>111,111</TabularNum>
        <div>9.99999</div>
        <TabularNum>9.99999</TabularNum>
        <div>1.11111</div>
        <TabularNum>1.11111</TabularNum>
      </PanelTable>
      <p>
        <CodeSnippet filename="CSS" language="css">
          {TabularNumsExampleStyles}
        </CodeSnippet>
      </p>
      <h5>Ligatures</h5>
      <p>
        Ligatures are special glyphs that replace two or more glyphs in order to better
        connect them. Common ligature replacements include ff, fi, fl, and ffi.
      </p>
      <SideBySideList>
        <li>
          <ExamplePanel fontSize="large">
            <FontNoLiga>ff, fi, fl</FontNoLiga>
          </ExamplePanel>
          <p>Without ligatures, the characters are all separate.</p>
        </li>
        <li>
          <ExamplePanel fontSize="large">
            <FontLiga>ff, fi, fl</FontLiga>
          </ExamplePanel>
          <p>With ligatures, the characters are connected into a single glyph.</p>
        </li>
      </SideBySideList>
      <p>
        <Flex gap={space(1)} align="flex-start">
          <PositiveLabel />
          Use ligatures across the whole user interface.
        </Flex>
      </p>
      <p>
        <CodeSnippet filename="CSS" language="css">
          {FontLigatureExampleStyles}
        </CodeSnippet>
      </p>
      <h5>Fractions</h5>
      <SideBySideList>
        <li>
          <ExamplePanel fontSize="large">1/12</ExamplePanel>
          <p>
            Rubik also contains special formatting for fractions. Without this formatting,
            numbers in fractions are just rendered as separate characters.
          </p>
        </li>
        <li>
          <ExamplePanel fontSize="large">
            <FontFractional>1/12</FontFractional>
          </ExamplePanel>
          <p>
            Fractional formatting shrinks the numbers and connects them with a diagonal
            slash, forming a proportional, condensed visual block.
          </p>
        </li>
      </SideBySideList>

      <p>
        <Flex gap={space(1)} align="flex-start">
          <PositiveLabel />
          Use fractional formatting whenever possible.
        </Flex>
      </p>
      <p>
        <CodeSnippet filename="CSS" language="css">
          {FontFractionExampleStyles}
        </CodeSnippet>
      </p>
    </FixedWidth>
  );
}

const FixedExternalLink = styled(ExternalLink)`
  color: ${p => p.theme.blue300};
  text-decoration: underline ${p => p.theme.blue100};

  :hover {
    color: ${p => p.theme.blue300};
    text-decoration: underline ${p => p.theme.blue200};
  }
`;

const FooterLink = styled(Link)`
  color: ${p => p.theme.gray300};

  :hover {
    color: ${p => p.theme.gray300};
    text-decoration: underline ${p => p.theme.gray200};
  }
`;

const SideBySideList = styled('ul')`
  /* Reset */
  list-style-type: none;
  margin: 0;
  padding: 0;
  & > li {
    margin: 0;
  }
  & > li > div {
    margin-bottom: 0;
  }

  /* Side-by-side display */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: ${space(2)};
`;

const ColorSwatch = styled(
  ({
    color,
    className,
    style,
  }: {
    color: ColorOrAlias;
    className?: string;
    style?: CSSProperties;
  }) => (
    <span className={className} style={style}>
      <IconCircleFill color={color} />
      {color}
    </span>
  )
)`
  display: inline-flex;
  gap: ${space(0.5)};
  align-items: center;

  border: 1px solid ${p => p.theme.border};
  border-radius: ${p => p.theme.borderRadius};
  padding: ${space(0.25)} ${space(0.5)};
  vertical-align: sub;
`;

const ExampleImg = styled('img')`
  border: 1px solid ${p => p.theme.border};
  border-radius: ${p => p.theme.borderRadius};
`;

const PositiveLabel = styled(
  ({className, style}: {className?: string; style?: CSSProperties}) => (
    <div className={className} style={style}>
      <IconCheckmark />
      DO
    </div>
  )
)`
  color: ${p => p.theme.green400};
  align-items: center;
  display: flex;
  font-weight: ${p => p.theme.fontWeightBold};
  gap: ${space(0.5)};
`;

const NegativeLabel = styled(
  ({className, style}: {className?: string; style?: CSSProperties}) => (
    <div className={className} style={style}>
      <IconClose color="red400" />
      DON'T
    </div>
  )
)`
  color: ${p => p.theme.red400};
  align-items: center;
  display: flex;
  font-weight: ${p => p.theme.fontWeightBold};
  gap: ${space(0.5)};
`;

const ExamplePanel = styled(Panel)<{fontSize?: 'large'}>`
  padding: ${space(2)};
  ${p =>
    p.fontSize === 'large'
      ? `
        font-weight: ${p.theme.fontWeightBold};
        font-size: 1.875rem;
        line-height: 1.2;
        letter-spacing: -0.016em;
      `
      : ''}
`;

const TabularNum = styled('div')`
  font-variant-numeric: tabular-nums;
`;

const FontLiga = styled('div')`
  /**
   * TODO: This should be applied to the root node of the side, why is that not the case?
   */
  font-feature-settings: 'liga';
`;
const FontNoLiga = styled('div')`
  /**
   * Using 'liga' is the default
   * We want to turn it off for the example.
   *
   * Don't copy+paste this!
   */
  font-feature-settings: 'liga' 0;
`;

const FontFractional = styled('div')`
  font-feature-settings: 'frac';
`;