import { css as gooberCss } from 'goober';
import { PropertiesHyphenFallback } from 'csstype';

interface CssString {
  // TODO stricter type checking
  toString(): string
}

export class CssStringImpl implements CssString {
  private css: string

  constructor(s: TemplateStringsArray) {
    this.css = s.raw[0]
      .trim()
      .replace(/[ ][ ]+/g, ' ')
      .replace(/:[ ]/g, ':')
      .replace(/\n/g, '')
      .replace(/;[ ]/g, ';');
  }

  toString() {
    return this.css
  }
}

interface CssIf {
  (b: boolean): (...a: CssString[]) => CssIfString
}

interface CssElse {
  (...c: CssString[]): CssString
  if: CssIf
}

interface CssIfString extends CssString {
  else: CssElse
}

const cssIf: CssIf = (b: boolean) => (...a: CssString[]) => new CssIfStringImpl(b, ...a);

export class CssIfStringImpl implements CssIfString {
  readonly else: CssElse
  private readonly cssStrings: CssString[]

  constructor(private readonly condition: boolean, ...cssStrings: CssString[]) {
    this.cssStrings = cssStrings

    const elseFn = (...c: CssString[]) => {
      if (this.condition) {
        return {
          toString: () => {
            return this.cssStrings.map((c2) => c2.toString()).join('');
          }
        }
      }
      return {
        toString: () => {
          return c.map((cc) => cc.toString()).join('')
        }
      };
    }
    elseFn.if = cssIf

    this.else = elseFn
  }

  toString() {
    if (this.condition) {
      return this.cssStrings.map((c2) => c2.toString()).join('');
    }
    return '';
  }
}

interface CssNest {
  (selector: string): (...a: CssString[]) => CssString
}

const cssNest: CssNest = (selector) => (...a) => {
  return {
    toString: () => `${selector}{${a.map(aa => aa.toString()).join('')}}`
  }
}

interface CssDynamic {
  (a: PropertiesHyphenFallback): CssString
}

const cssDynamic: CssDynamic = (a: PropertiesHyphenFallback) => {
  return {
    toString(): string {
      return Object
        .entries(a)
        .map(([key, value]) => `${key}:${value.toString()};`)
        .join('')
    }
  };
};

interface CssMedia {
  (raw: string): any
}

const cssMedia: CssMedia = (arg1: string) => {
  return (...body: CssString[]) => {
    return {
      toString(): string {
        return `@media ${arg1}{${body.map(b => b.toString()).join('')}}`
      }
    }
  }
}

const cssCompile = (...strings: CssString[]) => {
  return strings
    .filter((s) => ['string', 'object'].includes(typeof s))
    .map((s) => {
      if (typeof s === 'object') {
        return gooberCss`${s.toString()}`
      }

      return s
    })
    .join(' ')
}

interface Css {
  (s: TemplateStringsArray): CssString
  if: CssIf
  nest: CssNest
  dynamic: CssDynamic
  media: CssMedia
  cx(...strings: CssString[]): string
}

const _css: Partial<Css> = (s: TemplateStringsArray) => new CssStringImpl(s);
_css.if = cssIf;
_css.nest = cssNest;
_css.dynamic = cssDynamic;
_css.media = cssMedia;
_css.cx = cssCompile;

export const css = _css as Css;
