import $ from 'jquery';
const assetAttributeNames = new Set( ['data', 'srcset', 'src', 'href'] );

export interface Route {
  path: string,
  component: Function,
  wildcard: boolean
};

const routes: Route[] = [];
let err404page = "/";
let rootId = "moneyjsx-root";
let onprenavigate: Function = () => {};
let onpostnavigate: Function = () => {};

function routeForPath( route: string ) : Function | null {
  if( !routes[route] ) {
    for( let key of Object.keys( routes ) ) {
      const r = routes[key];

      if( r.wildcard ) {
        if( route.slice( 0, r.path.length ) == r.path ) {
          return r.component;
        }
      }
    }

    return null;
  } else return routes[route].component;
}

/**
 * sets the id of the element to be replaced by the navigator
 **/
export function setRootId( rootId: string ) {
  rootId = rootId;
}

/**
 * adds a route component to the routes list
 * the component function must return either a jquery or a DOM element
 **/
export function addRoute( name: string, component: Function ) {
  let path = name;
  let wildcard = false;
  if( path[path.length - 1] === "*" ) {
    console.log( "name" );
    path = path.substring( 0, path.length - 1 );
    wildcard = true;
  }
 
  routes[path] = { path, component, wildcard };
}

/**
 * sets the route for a 404 page
 **/
export function set404Route( name: string ) {
  err404page = name;
}

/**
 * sets the callback that will get called when a route changes
 **/
export function onPreNavigate( callback: Function ) {
  onprenavigate = callback;
}

/**
 * sets the callback that will get called when a route changes
 **/
export function onPostNavigate( callback: Function ) {
  onpostnavigate = callback;
}

/**
 * replaces the root element with the route component
 **/
export function navigate( route: string ) {
  console.log( route );
  let url = new URL( window.location.href );
  let cb = routeForPath( route );
  url.pathname = route;
  if( !cb )
    return navigate( err404page );

  window.history.pushState( {}, null, url.href );

  onprenavigate();
  const el = $( cb() );

  $( `#${rootId}` ).children().remove();
  $( `#${rootId}` ).append( el );
  onpostnavigate();
}

/**
 * navigate with params. see: navigate
 **/
export function navigateParams( route: string, params: any ) {
  let url = new URL( window.location.href );
  let uparams = new URLSearchParams( params );
  url.pathname = route;
  url.search = uparams.toString();
  let cb = routeForPath( route );
  if( !cb )
    return navigate( err404page );

  window.history.pushState( {}, null, url.href );

  onprenavigate();
  const el = $( cb() );

  $( `#${rootId}` ).children().remove();
  $( `#${rootId}` ).append( el );
  onpostnavigate();
}

/**
 * wrapper for history.pushState
 **/
export function pushParams( params: any ) {
  const url = new URL( window.location.href );
  url.search = new URLSearchParams( params ).toString();

  window.history.pushState( {}, null, url.href );
}

/**
 * navigates without adding a history entry
 * useful for e.g. re-rendering a page after waiting for a data callback
**/
export function navigateParamsSilent( route: string, params: any ) {
  let url = new URL( window.location.href );
  let uparams = new URLSearchParams( params );
  url.pathname = route;
  url.search = uparams.toString();
  let cb = routeForPath( route );
  if( !cb )
    return navigateSilent( err404page );

  onprenavigate();
  const el = $( cb() );

  $( `#${rootId}` ).children().remove();
  $( `#${rootId}` ).append( el );
  onpostnavigate();
}

/**
 * see: navigateParamsSilent
 **/
export function navigateSilent( route: string ) {
  let url = new URL( window.location.href );
  url.pathname = route;
  let cb = routeForPath( route );
  if( !cb )
    return navigateSilent( err404page );

  onprenavigate();
  const el = $( cb() );

  $( `#${rootId}` ).children().remove();
  $( `#${rootId}` ).append( el );
  onpostnavigate();
}

/**
 * action when the back button is pressed
**/
export function onPopState() {
  let url = new URL( window.location.href );
  let uparams = new URLSearchParams( url.searchParams );
  url.search = uparams.toString();
  let cb = routeForPath( url.pathname );
  if( !cb )
    return navigateSilent( err404page );

  onprenavigate();
  const el = $( cb() );

  $( `#${rootId}` ).children().remove();
  $( `#${rootId}` ).append( el );
  onpostnavigate();
}

export function getRoutes() : Route[] {
  return routes;
}


// internal stuff below

const originalAppendChild = Element.prototype.appendChild;
Element.prototype.appendChild = function( child: any ) {
  if( Array.isArray( child ) ) {
    for( const childArrayMember of child )
    this.appendChild( childArrayMember );

    return child;
  }
  else if( typeof child === 'string' ) {
    return originalAppendChild.call( this, document.createTextNode( child ) );
  }
  else if( child ) {
    return originalAppendChild.call( this, child );
  }
};

export function createElement( tag: any, props: any, ...children: any ) {
  props = props || {};

  if( typeof tag === "function" ) {
    props.children = children;
    return tag( props );
  }

  if( tag === 'raw-content' ) {
    const dummy     = document.createElement( 'div' );
    dummy.innerHTML = props.content;
    return [...dummy.children];
  }

  const element = document.createElement( tag );

  for( const [name, value] of Object.entries( props ) ) {
    if( name.startsWith( 'on' ) ) {
      const lowercaseName = name.toLowerCase();

      if( lowercaseName in window ) {
        element.addEventListener( lowercaseName.substring(2 ), value );
        continue;
      }
    }

    if( name == 'ref' ) {
      ( value as any ).current = element;
      continue;
    }

    if( value === false )
      continue;

    if( value === true ) {
      element.setAttribute( name, '' );
      continue;
    }

    if( assetAttributeNames.has( name ) ) {
      if( typeof value === 'string' ) {
        element.setAttribute( name, value );
      }
      continue;
    }

    element.setAttribute( name, value );
  };

  for( const child of children )
    element.appendChild( child );

  return element;
}

export function createFragment( props: any ) {
  return props.children;
}
