import React, { ReactNode } from "react"
import {
  withStreamlitConnection,
  StreamlitComponentBase,
  Streamlit,
} from "./streamlit"
import { Runtime, Inspector } from "@observablehq/runtime";

class Observable extends StreamlitComponentBase<{}> {
  public observeValue = {};
  private notebookRef = React.createRef<HTMLDivElement>();
  private runtime: any = null;
  private main: any = null;

  componentWillUnmount() {
    this.runtime?.dispose();
  }
  // @ts-ignore
  public componentDidUpdate(prevProps: any) {
    const { args: prevArgs } = prevProps;
    if (prevArgs.notebook !== this.props.args.notebook) {
      // TODO handle new notebook
    }
    console.log('this.props.args.redefine: ', this.props.args.redefine);
    if (this.main !== null) {
      this.redefineCells(this.main, this.props.args.redefine);
    }
  }

  async embedNotebook(notebook: string, targets: string[], observe: string[], hide:string[]) {
    if (this.runtime) {
      this.runtime.dispose();
    }
    
    console.log('Console says hi!');

    const targetSet = new Set(targets);
    const observeSet = new Set(observe);
    const hideSet = new Set(hide);
    this.runtime = new Runtime();
    const { default: define } = await eval(`import("https://api.observablehq.com/${notebook}.js?v=3")`);

    this.main = this.runtime.module(define, (name: string) => {
      console.log('name: ', name);
      console.log('observeSet.has(name: ', observeSet.has(name));
      console.log('targetSet.has(name): ', targetSet.has(name));
      if (observeSet.has(name) && !targetSet.has(name)) {
        const observeValue = this.observeValue;
    
        console.log('observeValue: ', observeValue);
    
        return {
          fulfilled: (value: any) => {
            //@ts-ignore
            observeValue[name] = value;
            //@ts-ignore
            Streamlit.setComponentValue(observeValue);
          }
        }
      }
      if (targetSet.size > 0 && !targetSet.has(name)) return;
      if(hideSet.has(name)) return true;
      const el = document.createElement('div');
      this.notebookRef.current?.appendChild(el);

      const i = new Inspector(el);
      el.addEventListener('input', e => {
        Streamlit.setFrameHeight();
      })
      return {
        pending() {
          i.pending();
          Streamlit.setFrameHeight();
        },
        fulfilled(value: any) {
          i.fulfilled(value);
          Streamlit.setFrameHeight();
        },
        rejected(error: any) {
          i.rejected(error);
          Streamlit.setFrameHeight();
        },
      };
    });
    if (observeSet.size > 0) {
      Promise.all(Array.from(observeSet).map(async name => [name, await this.main.value(name)])).then(initial => {
        for (const [name, value] of initial) {
          // @ts-ignore
          this.observeValue[name] = value
        };
        Streamlit.setComponentValue(this.observeValue);
      })
    }
  }

  redefineCells(main: any, redefine = {}) {
    
    console.log('Console says hi 2 !');

    for (let cell in redefine) {
      //@ts-ignore
      main.redefine(cell, redefine[cell]);
    }
  }
  componentDidMount() {
    const { notebook, targets = [], observe = [], redefine = {} , hide=[]} = this.props.args;
    Streamlit.setComponentValue(this.observeValue);
    this.embedNotebook(notebook, targets, observe, hide).then(() => {
      this.redefineCells(this.main, redefine);
    });

  }

  public render = (): ReactNode => {
    
    console.log('this.props.args.render_empty: ', this.props.args.render_empty);
    if (this.props.args.render_empty) {
      return (
        <div >
          <div style={{ padding: '9px 12px' }}>
            <div ref={this.notebookRef}></div>
          </div>
          <div style={{ marginTop: '4px' }}>
            
            <div >
              <div style={{textAlign:"left"}}>{this.props.args.name}</div>
              <div style={{textAlign:"right"}}>
              <a href={`https://observablehq.com/${this.props.args.notebook}`} style={{ color: '#666', }}></a>
              </div>
            </div>
          </div>
        </div >
      )
    }
    return (
      <div style={{ border: '1px solid gray', borderRadius: '4px' }}>
        <div style={{ padding: '9px 12px' }}>
          <div ref={this.notebookRef}></div>
        </div>
        <div style={{ marginTop: '4px' }}>
          
          <div style={{
            backgroundColor: '#ddd',
            fontWeight: 700,
            padding: ".25rem .5rem",
            borderRadius: '0 0 4px 4px',
            gridTemplateColumns: "auto auto",
            display:"grid"
          }}>
            <div style={{textAlign:"left"}}>{this.props.args.name}</div>
            <div style={{textAlign:"right"}}>
            <a href={`https://observablehq.com/${this.props.args.notebook}`} style={{ color: '#666', }}></a>
            </div>
          </div>
        </div>
      </div >
    )
  }
}

export default withStreamlitConnection(Observable)
